This commit is contained in:
mac
2025-05-09 14:04:45 +08:00
parent 4120fed91a
commit 15428ef516
51 changed files with 13364 additions and 43 deletions

View File

@@ -1,7 +1,7 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -645,7 +645,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -664,11 +664,11 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

228
README.md
View File

@@ -1,39 +1,189 @@
# 7.5寸三色日历
#### 介绍
这是一个7.5寸黑白红三色墨水开源项目,由群友 @遙遠的星辰 根据原作者 https://github.com/JADE-Jerry/jcalendar 开源项目修改而来,帮群友公开分享
当前项目使用的主控是 ESP32
#### 软件架构
软件架构说明
#### 安装教程
1. xxxx
2. xxxx
3. xxxx
#### 使用说明
1. xxxx
2. xxxx
3. xxxx
#### 参与贡献
1. Fork 本仓库
2. 新建 Feat_xxx 分支
3. 提交代码
4. 新建 Pull Request
#### 特技
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
# J-Calendar
墨水屏日历采用三色4.2寸墨水屏,展示基本月历信息,支持农历、公共假期、倒计日、天气(实时天气、每日天气)展示。<br>
项目以低难度、低成本的方式,方便爱好者实现属于自己的低功耗月历。<br>
<img src="./assets/img/sample.jpg" width="60%"><br>
Bilibili连接https://www.bilibili.com/video/BV1wHDhYoE3G/<br>
注:固件仅供个人下载免费使用,禁止商用。
## Prepare & Meterial
1. esp32开发板(建议lolin32 lite,其他esp32开发板亦可)<br>
2. 4.2寸三色墨水屏(400*300)。
3. 通用墨水屏驱动板。
4. 锂电池,需要做ph2.0接头。(建议603048, 900mah)
5. 金属底座亚克力台卡,95*80mm。
6. 轻触开关,(12\*12\*7.3,带键帽)
7. 工具:电烙铁、电线若干。
## Manufacture Guide:
1. 开发板接线<br>
* 墨水屏驱动板
* Busy->4
* RST->16
* DC->17
* CS->5
* SCK->18
* SDA->23
* GND->GND
* VCC->3V
* 其他
* 按钮->(PIN_14, GND)
* LED->22(板载)
2. 三色墨水屏排线插入时注意针脚方向,屏幕排线和驱动板排线1号针脚均是悬空,注意对齐。
3. 电池接口需要是ph2.0,且注意正负极(开发板上有标注),如果电池的正负极反了,可以用镊子调整电池插头。
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烧录过一次后就不需要重复烧录了
2. 选择Flash的配置信息
3. 选择连接的串口以及波特率(波特率可以根据实际情况调整)
4. 擦除Flash。
5. 开始烧录。<br>
<span style="color:red">文件前面一定要打勾否则不会刷进flash的</span><br>
(参考下图)<br>
<img src="./assets/img/flash_download_tool_guide.png" width="70%">
5. 安装盒3D打印用PLA或ABS均可。[E-ink box2 v22.3mf](./assets/file/E-ink%20box2%20v22.3mf)
## Button Operation Guide:
1. **单点**
如果处在休眠中,唤醒系统,并强制刷新月历。
如果处在运行中,强制刷新日历。
如果处在配置中,不做任何处理。
2. **双击**
系统正常运行中,双击进入系统配置。(并强制停止WIFI相关操作,如获取天气。)
系统配置状态中,双击重启系统。
3. **长按**
系统运行中,长按清除配置信息(WIFI密钥除外)和缓存,并重启。
## LED Indicator:
(板载LED,PIN-22)
1. 快闪: LED每秒闪约两次系统启动中(正在连接WIFI)
2. 常亮: WIFI连接完成。
3. 慢闪: LED每两秒闪一次WIFI连接失败10秒钟后休眠
4. 三短闪一长灭: 系统配置中。3分钟后超时休眠
5. 熄灭: 系统休眠。
## Web Config Guide:
通过在开机状态下(LED常亮)双击,即可进入配置状态,这时系统会生成一个名为J-Calendar的ap,默认密码为:password。(默认超时时间为180秒)
连接上ap后会直接弹出配置页面。(或者直接通过浏览器输入地址 http://192.168.4.1进入)。
配置页面:
1. Config Wifi. Wifi配置
进入配置wifi页面,选择搜索到的ap,并输入密码,并保存。
2. Setup. 系统配置
* 周首日:
* 0: 周日(默认); 1: 周一;
* 和风天气:
* 输入和风天气的API Key和城市id(城市对应的id请在和风天气的官网查找。)系统会每2小时刷新当前天气,如果Key置空,天气将不会被刷新。<br>
* 天气类型0:每日天气(默认,每天凌晨刷新一次); 1: 实时天气(每两个小时刷新一次天气)<br>
* 位置城市id或者经纬度 例1: 101010100 例2: 116.41,39.92<br>
[城市id列表](./assets/file/China-City-List-latest.csv) <br>
* 倒数日:<br>输入倒数日名称和日期,名称不能超过4个中文字符,时间以yyyyMMdd的格式填入。配置正确的话,日历每天会显示倒数“距****还有**天”。如果倒数日名称为空,系统将不显示倒数日信息。
* 日期Tag:<br>
1. 输入格式yyyyMMddxyyyy为年每年显示设为0000MM为月份每月显示设为00dd为日期x为tag的图标a:书签b金钱c笑脸d警告。例如00000015b每年每月15日旁边显示$符号00000312a每年3月12日显示书签符号。
2. 最多可以设置三个tag中间以分号隔开。例如00000015b;00000312a
* 保存配置后,系统自动重启。
3. Update. OTA升级
此项需要在浏览器内完成,通过ip地址访问配置页面,然后进入Update,选择固件文件后上传,等待。刷新完成后,页面会有成功提示。
4. Restart. 重启
在所有配置完后,需要重启生效。(也可以在配置状态下,双击按钮重启)
5. Info. 系统信息
可以监控系统的硬件情况,也可以在里面清除配置的Wifi密钥。
6. Exit. 退出
退出配置状态。
## Q & A
1. Q: 刷完机后,如何配置?启动流程是如何的?<br>
A: 需要在系统运行状态下状态灯常亮或慢闪时双击按键即可进入配置状态。LED灯变成三短闪一长灭时说明进入配置状态了。
2. Q: 刷完机后,没有反应该如何处理?<br>
A: 观察22针脚的LED是否点亮有闪烁说明固件已经刷入如果没有闪烁可以尝试点击重置按钮重启或者拔掉电源线和usb线重插来重启。此外还可使用串口工具通过usb的com口获取启动日志来进一步确认故障原因。
3. Q: 可以支持哪种屏?<br>
A: 仅4.2寸三色屏(目前仅支持黑白红,将来支持黑白黄)。
4. Q: 我使用的屏应该烧录哪个固件?<br>
A: 从经验上预估SES的拆机屏选z21比较新一些的选z98如果是非常老的屏选z15。实在不行的话三个固件都刷一遍试试。<br>
明确的丝印对应如下:<br>
|丝印|固件|
|-----|-----|
|E042A43-A0|z98|
|P420010|z98|
|A13600**|z21|
5. Q: 使用flash download tool刷新固件时报错连接串口失败。<br>
A: 1. 检查USB线连接是否正常。 <br>2. app的串口下拉框里是否检测到COM口。 <br>3. 把其他串口工具关闭防止占用COM口。
6. Q: 刷新固件的过程中提示报错。<br>
A: 1. 换根质量较好的或短一些的USB线或换个USB口插入。 <br>2. 可以将刷新的波特率降低一点如选择速率低一点的115200。
7. Q: 刷新固件后,需要重新配置吗?
A: 仅刷新app固件后配置是保留的所以无需重新配置。如果刷新了分区表partition.bin会将esp的nvs区刷新这时候需要重新配置。
8. Q: 填入和风天气的API Key和位置ID后没有成功获取天气信息。
A: 3月1日后注册的和风天气账户有API Host限制请下载1.0.25以后版本在配置时配置API Host信息。
## Releases
### 1.0.25 (未测试)
* Fix: 和风天气api变更需要输入API Host在配置页面增加配置项
### 1.0.24
* Refine: 配置页面location约束改为64长度允许输入经纬度设置天气位置e.g. 116.41,39.92
### 1.0.23
* Refine: 修改和风天气请求url。
* Fix: bug(获取holiday返回码)。
### 1.0.22
* 项目开源此项目使用GNU General Public License v3.0许可证授权。详情请参阅LICENSE文件。
* Refine农历计算功能移出。农历功能作为独立库以MIT协议开源
### 1.0.21
* Fix: bug (wrong background color of calender header).
### 1.0.20
* Fix: bugs.
### 1.0.19
* New: 假日信息通过网络API获取。
* Refine: 由于esp32内置时钟的误差会有可能唤醒的时间不准确同时由于处于午夜0点左右导致提前刷新或在刷新期间切换日期导致日期显示不准确。处理方式计时器唤醒的情况下23:50以后不刷新直接休眠等待至0点以后唤醒刷新。
### 1.0.18
* Fix: 修正Wifi连接失败后同步时间导致系统时间错误。
### 1.0.17
* Refine: 如果wifi连接失败等待10s再休眠并增加LED慢闪作为指示。在这时间段内供用户进行双击打开配置状态
* Fix: 修正倒数日名称显示不足的问题。
### 1.0.16
* New Feature: UI调整新增今日农历日期的展现。
### 1.0.15
* Fix: 编译异常。
### 1.0.14
* Fix: bug(假日颜色不正确), 增加显示假日和调休icon(日历右上角)。
### 1.0.13
* Fix: bugs.
### 1.0.12
* 新功能:增加设置每周首日,默认首日为周日。
### 1.0.11
* 优化:补充部分公历&农历节日
### 1.0.10
* 新功能增加日期Tag通过配置页面进行配置
### 1.0.9
* 优化:
1. 统一每日天气与实时天气的布局。
2. 增加时间容错性当同步网络时间失败的时候使用天气api的更新时间。
* 新功能:增加节日显示(元旦、劳动节、国庆节)
### 1.0.8
* 优化显示:加粗当前日期框;不再使用局刷,全部改为全屏刷新(提高显示的清晰度,兼容不支持局部刷新的三色屏)。
* 预置2025年公休假信息。
### 1.0.7
* 增加24节气显示。修复部分bug。
### 1.0.6
* 增加天气模式--*每日天气*(默认配置),每天刷新一次。(增加月历待机时长。部分不支持局刷的屏可以选择这个模式。)
### 1.0.5
* 增加强制刷新处理(单击按钮)。
* 修复日历功能、修复日历字典数据
* 修复部分bug。
### 1.0.4<br>
* 增加倒计日功能
* 修复部分bug。
### 1.0.3
* 基本功能
* 功耗优化等
## License
此项目使用GNU General Public License v3.0许可证授权。详情请参阅LICENSE文件。
### ★★★固件还不错,支持一下★★★
<img src="./assets/img/buymeacoffee.jpg" width="32.9%">
<img src="./assets/img/likeit.jpg" width="30%"><br>
## Reference:
1. \<WEMOS LOLIN32簡介\> https://swf.com.tw/?p=1331&cpage=1
2. \<GxEPD2\> https://github.com/ZinggJM/GxEPD2
3. \<U8g2_for_Adafruit_GFX\> https://github.com/olikraus/U8g2_for_Adafruit_GFX
4. \<和风天气\> https://dev.qweather.com/docs/api/weather/weather-now/
<br>
Copyright © 2023-2025. All Rights Reserved.

BIN
assets/.DS_Store vendored Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
assets/img/LOLIN32_Lite.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
assets/img/buymeacoffee.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

BIN
assets/img/likeit.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
assets/img/sample.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

43
bin/build.sh Normal file
View File

@@ -0,0 +1,43 @@
export PATH=/Users/jerry/.platformio/penv/bin:/Users/jerry/.platformio/penv:/Users/jerry/.platformio/python3/bin:$PATH
# function
cd ..
basedir=$PWD
### compile ###
cd $basedir
if [ $# -ne 1 ]; then
echo "ERR: parameter [VERSION] is required."
echo "Usage: ./build.sh [VERSION]"
echo " e.g. ./build.sh 1.0.5"
exit 1
fi
version=$1
echo $version
rm -f ./dist/*
pio run -t clean
export PLATFORMIO_BUILD_FLAGS=-DJ_VERSION=\\\"${version}\\\"
pio run -e z98 -e z21 -e z15
if [ $? -ne 0 ]; then
echo "ERR: build err when firmware of Z98."
exit 1
else
cp $basedir/.pio/build/z98/bootloader.bin $basedir/dist/bootloader.bin
cp $basedir/.pio/build/z98/partitions.bin $basedir/dist/partitions.bin
cp $basedir/.pio/build/z98/firmware.bin $basedir/dist/jcalendar_${version}_z98.bin
cp $basedir/.pio/build/z15/firmware.bin $basedir/dist/jcalendar_${version}_z15.bin
cp $basedir/.pio/build/z21/firmware.bin $basedir/dist/jcalendar_${version}_z21.bin
fi
echo
echo
echo
echo "::: Build finish. :::"
echo " Please commit & push to Github."

BIN
dist/bootloader.bin vendored Normal file

Binary file not shown.

BIN
dist/jcalendar_1.0.25_z15.bin vendored Normal file

Binary file not shown.

BIN
dist/jcalendar_1.0.25_z21.bin vendored Normal file

Binary file not shown.

BIN
dist/jcalendar_1.0.25_z98.bin vendored Normal file

Binary file not shown.

BIN
dist/partitions.bin vendored Normal file

Binary file not shown.

23
extra_script.py Normal file
View File

@@ -0,0 +1,23 @@
Import("env")
import os
def merge_bin(source, target, env):
firmware_path = os.path.join(env.subst("$BUILD_DIR"), "firmware.bin")
bootloader_path = os.path.join(env.subst("$BUILD_DIR"), "bootloader.bin")
partitions_path = os.path.join(env.subst("$BUILD_DIR"), "partitions.bin")
filesystem_path = os.path.join(env.subst("$BUILD_DIR"), "littlefs.bin")
output_path = os.path.join(env.subst("$PROJECT_DIR"), "/dist/firmware.bin")
cmd = [
"esptool.py",
"--chip", "esp32",
"merge_bin",
"-o", output_path,
"0x1000", bootloader_path,
"0x8000", partitions_path,
"0x10000", firmware_path,
"0x3D0000", filesystem_path
]
env.Execute(" ".join(cmd))
env.AddPostAction("buildprog", merge_bin)

View File

@@ -0,0 +1,339 @@
// Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines!
//
// Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/
//
// Author: Jean-Marc Zingg
//
// Version: see library.properties
//
// Library: https://github.com/ZinggJM/GxEPD2
// Supporting Arduino Forum Topics (closed, read only):
// Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657
// Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865
//
// Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues
// NOTE: you may need to adapt or select for your wiring in the processor specific conditional compile sections below
// select the display class (only one), matching the kind of display panel
//#define GxEPD2_DISPLAY_CLASS GxEPD2_BW
#define GxEPD2_DISPLAY_CLASS GxEPD2_3C
//#define GxEPD2_DISPLAY_CLASS GxEPD2_4C
//#define GxEPD2_DISPLAY_CLASS GxEPD2_7C
// 脚本编译需要,根据命令行参数编译不同版本固件。
#if defined(SI_DRIVER)
#if (SI_DRIVER == 750)
#define GxEPD2_DRIVER_CLASS GxEPD2_750c
#elif (SI_DRIVER == 21)
#define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21
#elif (SI_DRIVER == 98)
#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY042Z98
#endif
#endif
// 默认固件
#ifndef GxEPD2_DRIVER_CLASS
// #define GxEPD2_DRIVER_CLASS GxEPD2_420c // Z15
// #define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21 // Z21
#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY042Z98 // Z98
#endif
// select the display driver class (only one) for your panel
//#define GxEPD2_DRIVER_CLASS GxEPD2_102 // GDEW0102T4 80x128, UC8175, (WFT0102CZA2)
//#define GxEPD2_DRIVER_CLASS GxEPD2_150_BN // DEPG0150BN 200x200, SSD1681, (FPC8101), TTGO T5 V2.4.1
//#define GxEPD2_DRIVER_CLASS GxEPD2_154 // GDEP015OC1 200x200, IL3829, (WFC0000CZ07), no longer available
//#define GxEPD2_DRIVER_CLASS GxEPD2_154_D67 // GDEH0154D67 200x200, SSD1681, (HINK-E154A07-A1)
//#define GxEPD2_DRIVER_CLASS GxEPD2_154_T8 // GDEW0154T8 152x152, UC8151 (IL0373), (WFT0154CZ17)
//#define GxEPD2_DRIVER_CLASS GxEPD2_154_M09 // GDEW0154M09 200x200, JD79653A, (WFT0154CZB3)
//#define GxEPD2_DRIVER_CLASS GxEPD2_154_M10 // GDEW0154M10 152x152, UC8151D, (WFT0154CZ17)
//#define GxEPD2_DRIVER_CLASS GxEPD2_154_GDEY0154D67 // GDEY0154D67 200x200, SSD1681, (FPC-B001 20.05.21)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213 // GDE0213B1 122x250, IL3895, (HINK-E0213-G01), phased out
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B72 // GDEH0213B72 122x250, SSD1675A (IL3897), (HINK-E0213A22-A0 SLH1852)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B73 // GDEH0213B73 122x250, SSD1675B, (HINK-E0213A22-A0 SLH1914)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_B74 // GDEM0213B74 122x250, SSD1680, FPC-7528B)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_flex // GDEW0213I5F 104x212, UC8151 (IL0373), (WFT0213CZ16)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_M21 // GDEW0213M21 104x212, UC8151 (IL0373), (WFT0213CZ16)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_T5D // GDEW0213T5D 104x212, UC8151D, (WFT0213CZ16)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_BN // DEPG0213BN 122x250, SSD1680, (FPC-7528B), TTGO T5 V2.4.1, V2.3.1
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_GDEY0213B74 // GDEY0213B74 122x250, SSD1680, (FPC-A002 20.04.08)
//#define GxEPD2_DRIVER_CLASS GxEPD2_260 // GDEW026T0 152x296, UC8151 (IL0373), (WFT0154CZ17)
//#define GxEPD2_DRIVER_CLASS GxEPD2_260_M01 // GDEW026M01 152x296, UC8151 (IL0373), (WFT0260CZB2)
//#define GxEPD2_DRIVER_CLASS GxEPD2_266_BN // DEPG0266BN 152x296, SSD1680, (FPC7510), TTGO T5 V2.66, TTGO T5 V2.4.1
//#define GxEPD2_DRIVER_CLASS GxEPD2_266_GDEY0266T90 // GDEY0266T90 152x296, SSD1680, (FPC-A003 HB)
//#define GxEPD2_DRIVER_CLASS GxEPD2_270 // GDEW027W3 176x264, EK79652 (IL91874), (WFI0190CZ22)
//#define GxEPD2_DRIVER_CLASS GxEPD2_270_GDEY027T91 // GDEY027T91 176x264, SSD1680, (FB)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290 // GDEH029A1 128x296, SSD1608 (IL3820), (E029A01-FPC-A1 SYX1553)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5 // GDEW029T5 128x296, UC8151 (IL0373), (WFT0290CZ10)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T5D // GDEW029T5D 128x296, UC8151D, (WFT0290CZ10)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_I6FD // GDEW029I6FD 128x296, UC8151D, (WFT0290CZ10)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94 // GDEM029T94 128x296, SSD1680, (FPC-7519 rev.b)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_T94_V2 // GDEM029T94 128x296, SSD1680, (FPC-7519 rev.b), Waveshare 2.9" V2 variant
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_BS // DEPG0290BS 128x296, SSD1680, (FPC-7519 rev.b)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_M06 // GDEW029M06 128x296, UC8151D, (WFT0290CZ10)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T94 // GDEY029T94 128x296, SSD1680, (FPC-A005 20.06.15)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_GDEY029T71H // GDEY029T71H 168x384, SSD1685, (FPC-H004 22.03.24)
//#define GxEPD2_DRIVER_CLASS GxEPD2_310_GDEQ031T10 // GDEQ031T10 240x320, UC8253, (no inking, backside mark KEGMO 3100)
//#define GxEPD2_DRIVER_CLASS GxEPD2_371 // GDEW0371W7 240x416, UC8171 (IL0324), (missing)
//#define GxEPD2_DRIVER_CLASS GxEPD2_370_TC1 // ED037TC1 280x480, SSD1677, (ICA-FU-20 ichia 2029), Waveshare 3.7"
//#define GxEPD2_DRIVER_CLASS GxEPD2_420 // GDEW042T2 400x300, UC8176 (IL0398), (WFT042CZ15)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420_M01 // GDEW042M01 400x300, UC8176 (IL0398), (WFT042CZ15)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420_GDEY042T81 // GDEY042T81 400x300, SSD1683 (no inking)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420_GYE042A87 // GYE042A87, 400x300, SSD1683 (HINK-E042-A07-FPC-A1)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420_SE0420NQ04 // SE0420NQ04, 400x300, UC8276C (OPM042A2_V1.0)
//#define GxEPD2_DRIVER_CLASS GxEPD2_426_GDEQ0426T82 // GDEQ0426T82 480x800, SSD1677 (P426010-MF1-A)
//#define GxEPD2_DRIVER_CLASS GxEPD2_579_GDEY0579T93 // GDEY0579T93 792x272, SSD1683 (FPC-E004 22.04.13)
//#define GxEPD2_DRIVER_CLASS GxEPD2_583 // GDEW0583T7 600x448, UC8159c (IL0371), (missing)
//#define GxEPD2_DRIVER_CLASS GxEPD2_583_T8 // GDEW0583T8 648x480, EK79655 (GD7965), (WFT0583CZ61)
//#define GxEPD2_DRIVER_CLASS GxEPD2_583_GDEQ0583T31 // GDEQ0583T31 648x480, UC8179, (P583010-MF1-B)
//#define GxEPD2_DRIVER_CLASS GxEPD2_750 // GDEW075T8 640x384, UC8159c (IL0371), (WF0583CZ09)
//#define GxEPD2_DRIVER_CLASS GxEPD2_750_T7 // GDEW075T7 800x480, EK79655 (GD7965), (WFT0583CZ61)
//#define GxEPD2_DRIVER_CLASS GxEPD2_750_GDEY075T7 // GDEY075T7 800x480, UC8179 (GD7965), (FPC-C001 20.08.20)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1020_GDEM102T91 // GDEM102T91 960x640, SSD1677, (FPC7705 REV.b)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1085_GDEM1085T51 // GDEM1085T51 1360x480, JD79686AB, (FPC8617) *** needs CS2 ***
//#define GxEPD2_DRIVER_CLASS GxEPD2_1160_T91 // GDEH116T91 960x640, SSD1677, (none or hidden)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1248 // GDEW1248T3 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1330_GDEM133T91 // GDEM133T91 960x680, SSD1677, (FPC-7701 REV.B)
// 3-color e-papers
//#define GxEPD2_DRIVER_CLASS GxEPD2_154c // GDEW0154Z04 200x200, IL0376F, (WFT0000CZ04), no longer available
//#define GxEPD2_DRIVER_CLASS GxEPD2_154_Z90c // GDEH0154Z90 200x200, SSD1681, (HINK-E154A07-A1)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213c // GDEW0213Z16 104x212, UC8151 (IL0373), (WFT0213CZ16)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z19c // GDEH0213Z19 104x212, UC8151D, (HINK-E0213A20-A2 2020-11-19)
//#define GxEPD2_DRIVER_CLASS GxEPD2_213_Z98c // GDEY0213Z98 122x250, SSD1680, (FPC-A002 20.04.08)
//#define GxEPD2_DRIVER_CLASS GxEPD2_266c // GDEY0266Z90 152x296, SSD1680, (FPC-7510)
//#define GxEPD2_DRIVER_CLASS GxEPD2_270c // GDEW027C44 176x264, IL91874, (WFI0190CZ22)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290c // GDEW029Z10 128x296, UC8151 (IL0373), (WFT0290CZ10)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_Z13c // GDEH029Z13 128x296, UC8151D, (HINK-E029A10-A3 20160809)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290_C90c // GDEM029C90 128x296, SSD1680, (FPC-7519 rev.b)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420c // GDEW042Z15 400x300, UC8176 (IL0398), (WFT0420CZ15)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_Z21 // GDEQ042Z21 400x300, UC8276, (hidden)
//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY042Z98 // GDEY042Z98 400x300, SSD1683 (no inking)
//#define GxEPD2_DRIVER_CLASS GxEPD2_579c_GDEY0579Z93 // GDEY0579Z93 792x272, SSD1683 (FPC-E004 22.04.13)
//#define GxEPD2_DRIVER_CLASS GxEPD2_583c // GDEW0583Z21 600x448, UC8159c (IL0371), (missing)
//#define GxEPD2_DRIVER_CLASS GxEPD2_583c_Z83 // GDEW0583Z83 648x480, EK79655 (GD7965), (WFT0583CZ61)
//#define GxEPD2_DRIVER_CLASS GxEPD2_583c_GDEQ0583Z31 // GDEQ0583Z31 648x480, UC8179C,
//#define GxEPD2_DRIVER_CLASS GxEPD2_750c // GDEW075Z09 640x384, UC8159c (IL0371), (WF0583CZ09)
//#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z08 // GDEW075Z08 800x480, EK79655 (GD7965), (WFT0583CZ61)
//#define GxEPD2_DRIVER_CLASS GxEPD2_750c_Z90 // GDEH075Z90 880x528, SSD1677, (HINK-E075A07-A0)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1160c_GDEY116Z91 // GDEY116Z91 960x640, SSD1677, (FPV-K002 22.04.15)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1248c // GDEY1248Z51 1304x984, UC8179, (WFT1248BZ23,WFT1248BZ24)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1330c_GDEM133Z91 // GDEM133Z91 960x680, SSD1677 (FPC-7701 REV.B)
// 4-color e-paper
//#define GxEPD2_DRIVER_CLASS GxEPD2_213c_GDEY0213F51 // GDEY0213F51 122x250, JD79661 (FPC-A002 20.04.08)
//#define GxEPD2_DRIVER_CLASS GxEPD2_266c_GDEY0266F51H // GDEY0266F51H 184x360, JD79667 (FPC-H006 22.04.02)
//#define GxEPD2_DRIVER_CLASS GxEPD2_290c_GDEY029F51H // GDEY029F51H 168x384, JD79667 (FPC-H004 22.03.24)
//#define GxEPD2_DRIVER_CLASS GxEPD2_300c // Waveshare 3.00" 4-color
//#define GxEPD2_DRIVER_CLASS GxEPD2_420c_GDEY0420F51 // GDEY0420F51 400x300, HX8717 (no inking)
//#define GxEPD2_DRIVER_CLASS GxEPD2_437c // Waveshare 4.37" 4-color
//#define GxEPD2_DRIVER_CLASS GxEPD2_0579c_GDEY0579F51 // GDEY0579F51 792x272, HX8717 (FPC-E009 22.09.25)
//#define GxEPD2_DRIVER_CLASS GxEPD2_1160c_GDEY116F51 // GDEY116F51 960x640, SSD2677, (FPC-K012 23.09.27)
// 7-color e-paper
//#define GxEPD2_DRIVER_CLASS GxEPD2_565c // Waveshare 5.65" 7-color
//#define GxEPD2_DRIVER_CLASS GxEPD2_565c_GDEP0565D90 // GDEP0565D90 600x448 7-color (E219454, AB1024-EGA AC0750TC1)
//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEY073D46 // GDEY073D46 800x480 7-color, (N-FPC-001 2021.11.26)
//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_ACeP_730 // Waveshare 7.3" 7-color
//#define GxEPD2_DRIVER_CLASS GxEPD2_730c_GDEP073E01 // GDEP073E01 800x480 7-color, (E350911HF 94V-0 F-6 ROHS 24141)
// grey levels parallel IF e-papers on Waveshare e-Paper IT8951 Driver HAT
//#define GxEPD2_DRIVER_CLASS GxEPD2_it60 // ED060SCT 800x600
//#define GxEPD2_DRIVER_CLASS GxEPD2_it60_1448x1072 // ED060KC1 1448x1072
//#define GxEPD2_DRIVER_CLASS GxEPD2_it78_1872x1404 // ED078KC2 1872x1404
//#define GxEPD2_DRIVER_CLASS GxEPD2_it103_1872x1404 // ES103TC1 1872x1404
// SS is usually used for CS. define here for easy change
#ifndef EPD_CS
#define EPD_CS SS
#endif
#if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS)
// somehow there should be an easier way to do this
#define GxEPD2_BW_IS_GxEPD2_BW true
#define GxEPD2_3C_IS_GxEPD2_3C true
#define GxEPD2_4C_IS_GxEPD2_4C true
#define GxEPD2_7C_IS_GxEPD2_7C true
#define GxEPD2_1248_IS_GxEPD2_1248 true
#define GxEPD2_1248c_IS_GxEPD2_1248c true
#define IS_GxEPD(c, x) (c##x)
#define IS_GxEPD2_BW(x) IS_GxEPD(GxEPD2_BW_IS_, x)
#define IS_GxEPD2_3C(x) IS_GxEPD(GxEPD2_3C_IS_, x)
#define IS_GxEPD2_4C(x) IS_GxEPD(GxEPD2_4C_IS_, x)
#define IS_GxEPD2_7C(x) IS_GxEPD(GxEPD2_7C_IS_, x)
#define IS_GxEPD2_1248(x) IS_GxEPD(GxEPD2_1248_IS_, x)
#define IS_GxEPD2_1248c(x) IS_GxEPD(GxEPD2_1248c_IS_, x)
#include "GxEPD2_selection_check.h"
#if defined (ESP8266)
#define MAX_DISPLAY_BUFFER_SIZE (81920ul-34000ul-5000ul) // ~34000 base use, change 5000 to your application use
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=D8*/ EPD_CS, /*DC=D3*/ 0, /*RST=D4*/ 2, /*BUSY=D2*/ 4));
// mapping of Waveshare e-Paper ESP8266 Driver Board, new version
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=2*/ 2, /*BUSY=5*/ 5));
// mapping of Waveshare e-Paper ESP8266 Driver Board, old version
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=15*/ EPD_CS, /*DC=4*/ 4, /*RST=5*/ 5, /*BUSY=16*/ 16));
#undef MAX_DISPLAY_BUFFER_SIZE
#undef MAX_HEIGHT
#endif
#if defined(ESP32)
#define MAX_DISPLAY_BUFFER_SIZE 65536ul // e.g.
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
#if !IS_GxEPD2_1248(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_1248c(GxEPD2_DRIVER_CLASS)
#if defined(ARDUINO_LOLIN_D32_PRO)
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 0, /*RST=*/ 2, /*BUSY=*/ 15)); // my LOLIN_D32_PRO proto board
#elif defined(ARDUINO_LOLIN_S2_MINI)
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS*/ 33, /*DC=*/ 35, /*RST=*/ 37, /*BUSY=*/ 39)); // my LOLIN ESP32 S2 mini connection
#else
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13)); // Good Display ESP32 Development Kit ESP32-L
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 27, /*DC=*/ 14, /*RST=*/ 12, /*BUSY=*/ 13, /*CS2=*/ 4)); // for GDEM1085T51 with ESP32-L
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // my suggested wiring and proto board
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ 5, /*DC=*/ 17, /*RST=*/ 16, /*BUSY=*/ 4)); // LILYGO_T5_V2.4.1
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 19, /*RST=*/ 4, /*BUSY=*/ 34)); // LILYGO® TTGO T5 2.66
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=5*/ EPD_CS, /*DC=*/ 2, /*RST=*/ 0, /*BUSY=*/ 4)); // e.g. TTGO T8 ESP32-WROVER
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 15, /*DC=*/ 27, /*RST=*/ 26, /*BUSY=*/ 25)); // Waveshare ESP32 Driver Board
#endif
#else // GxEPD2_1248 or GxEPD2_1248c
// Waveshare 12.48 b/w or b/w/r SPI display board and frame or Good Display 12.48 b/w panel GDEW1248T3 or b/w/r panel GDEY1248Z51
// general constructor for use with all parameters, e.g. for Waveshare ESP32 driver board mounted on connection board
GxEPD2_DISPLAY_CLASS < GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS) > display(GxEPD2_DRIVER_CLASS(/*sck=*/ 13, /*miso=*/ 12, /*mosi=*/ 14,
/*cs_m1=*/ 23, /*cs_s1=*/ 22, /*cs_m2=*/ 16, /*cs_s2=*/ 19,
/*dc1=*/ 25, /*dc2=*/ 17, /*rst1=*/ 33, /*rst2=*/ 5,
/*busy_m1=*/ 32, /*busy_s1=*/ 26, /*busy_m2=*/ 18, /*busy_s2=*/ 4));
#endif
#undef MAX_DISPLAY_BUFFER_SIZE
#undef MAX_HEIGHT
#endif
// can't use package "STMF1 Boards (STM32Duino.com)" (Roger Clark) anymore with Adafruit_GFX, use "STM32 Boards (selected from submenu)" (STMicroelectronics)
#if defined(ARDUINO_ARCH_STM32)
#define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
// for Good Display STM32 Development Kit DESPI-L.
// needs jumpers from PA5 (PIN_SPI_SCK) to SCK for EPD and PA7 (PIN_SPI_MOSI) to SDI for EPD. PD9 and PD10 are not HW SPI capable.
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ PD8, /*DC=*/ PE15, /*RST=*/ PE14, /*BUSY=*/ PE13));
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ PD8, /*DC=*/ PE15, /*RST=*/ PE14, /*BUSY=*/ PE13, /*CS2=*/ PD12)); // for GDEM1085T51
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=PA4*/ EPD_CS, /*DC=*/ PA3, /*RST=*/ PA2, /*BUSY=*/ PA1)); // my proto board
#undef MAX_DISPLAY_BUFFER_SIZE
#undef MAX_HEIGHT
#endif
#if defined(__AVR)
#if defined (ARDUINO_AVR_MEGA2560) // Note: SS is on 53 on MEGA
#define MAX_DISPLAY_BUFFER_SIZE 5000 // e.g. full height for 200x200
#else // Note: SS is on 10 on UNO, NANO
#define MAX_DISPLAY_BUFFER_SIZE 800 //
#endif
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
// for Arduino Micro or Arduino Leonardo with CS on 10 on my proto boards (SS would be 17) uncomment instead:
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 10, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#endif
#if defined(ARDUINO_UNOR4_MINIMA) || defined(ARDUINO_UNOR4_WIFI)
#define MAX_DISPLAY_BUFFER_SIZE 16384ul // e.g. half of available RAM
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#endif
#if defined(ARDUINO_ARCH_SAM)
#define MAX_DISPLAY_BUFFER_SIZE 32768ul // e.g., up to 96k
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=10*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#undef MAX_DISPLAY_BUFFER_SIZE
#undef MAX_HEIGHT
#endif
#if defined(ARDUINO_ARCH_SAMD)
#define MAX_DISPLAY_BUFFER_SIZE 15000ul // ~15k is a good compromise
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
// adapt the constructor parameters to your wiring
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 7, /*RST=*/ 6, /*BUSY=*/ 5));
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 4, /*DC=*/ 3, /*RST=*/ 2, /*BUSY=*/ 1)); // my Seed XIOA0
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=4*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // my other Seed XIOA0
#undef MAX_DISPLAY_BUFFER_SIZE
#undef MAX_HEIGHT
#endif
#if defined(ARDUINO_ARCH_RP2040)
#define MAX_DISPLAY_BUFFER_SIZE 131072ul // e.g. half of available ram
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8) ? EPD::HEIGHT : MAX_DISPLAY_BUFFER_SIZE / (EPD::WIDTH / 8))
#elif IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) || IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE / 2) / (EPD::WIDTH / 8))
#elif IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS)
#define MAX_HEIGHT(EPD) (EPD::HEIGHT <= (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2) ? EPD::HEIGHT : (MAX_DISPLAY_BUFFER_SIZE) / (EPD::WIDTH / 2))
#endif
#if defined(ARDUINO_NANO_RP2040_CONNECT)
// adapt the constructor parameters to your wiring
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ EPD_CS, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7));
#endif
#if defined(ARDUINO_RASPBERRY_PI_PICO)
// adapt the constructor parameters to your wiring
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 5, /*DC=*/ 8, /*RST=*/ 9, /*BUSY=*/ 7)); // my proto board
// mapping of GoodDisplay DESPI-PICO. NOTE: uses alternate HW SPI pins!
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 1, /*BUSY=*/ 0)); // DESPI-PICO
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 3, /*DC=*/ 2, /*RST=*/ 11, /*BUSY=*/ 10)); // DESPI-PICO modified
//GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 9, /*DC=*/ 8, /*RST=*/ 12, /*BUSY=*/ 13)); // Waveshare Pico-ePaper-2.9
#endif
#if defined(ARDUINO_RASPBERRY_PI_PICO_W)
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ 9, /*DC=*/ 8, /*RST=*/ 12, /*BUSY=*/ 13)); // Waveshare Pico-ePaper-2.9
#endif
#if defined(ARDUINO_ADAFRUIT_FEATHER_RP2040_THINKINK)
// Adafruit Feather RP2040 ThinkInk used with package https://github.com/earlephilhower/arduino-pico
GxEPD2_DISPLAY_CLASS<GxEPD2_DRIVER_CLASS, MAX_HEIGHT(GxEPD2_DRIVER_CLASS)> display(GxEPD2_DRIVER_CLASS(/*CS=*/ PIN_EPD_CS, /*DC=*/ PIN_EPD_DC, /*RST=*/ PIN_EPD_RESET, /*BUSY=*/ PIN_EPD_BUSY));
#endif
#undef MAX_DISPLAY_BUFFER_SIZE
#undef MAX_HEIGHT
#endif
#endif

View File

@@ -0,0 +1,156 @@
// Display Library example for SPI e-paper panels from Dalian Good Display and boards from Waveshare.
// Requires HW SPI and Adafruit_GFX. Caution: the e-paper panels require 3.3V supply AND data lines!
//
// Display Library based on Demo Example from Good Display: https://www.good-display.com/companyfile/32/
//
// Author: Jean-Marc Zingg
//
// Version: see library.properties
//
// Library: https://github.com/ZinggJM/GxEPD2
// Supporting Arduino Forum Topics (closed, read only):
// Good Display ePaper for Arduino: https://forum.arduino.cc/t/good-display-epaper-for-arduino/419657
// Waveshare e-paper displays with SPI: https://forum.arduino.cc/t/waveshare-e-paper-displays-with-spi/467865
//
// Add new topics in https://forum.arduino.cc/c/using-arduino/displays/23 for new questions and issues
#define GxEPD2_102_IS_BW true
#define GxEPD2_150_BN_IS_BW true
#define GxEPD2_154_IS_BW true
#define GxEPD2_154_D67_IS_BW true
#define GxEPD2_154_T8_IS_BW true
#define GxEPD2_154_M09_IS_BW true
#define GxEPD2_154_M10_IS_BW true
#define GxEPD2_154_GDEY0154D67_IS_BW true
#define GxEPD2_213_IS_BW true
#define GxEPD2_213_B72_IS_BW true
#define GxEPD2_213_B73_IS_BW true
#define GxEPD2_213_B74_IS_BW true
#define GxEPD2_213_flex_IS_BW true
#define GxEPD2_213_M21_IS_BW true
#define GxEPD2_213_T5D_IS_BW true
#define GxEPD2_213_BN_IS_BW true
#define GxEPD2_213_GDEY0213B74_IS_BW true
#define GxEPD2_260_IS_BW true
#define GxEPD2_260_M01_IS_BW true
#define GxEPD2_266_BN_IS_BW true
#define GxEPD2_266_GDEY0266T90_IS_BW true
#define GxEPD2_270_IS_BW true
#define GxEPD2_270_GDEY027T91_IS_BW true
#define GxEPD2_290_IS_BW true
#define GxEPD2_290_T5_IS_BW true
#define GxEPD2_290_T5D_IS_BW true
#define GxEPD2_290_I6FD_IS_BW true
#define GxEPD2_290_T94_IS_BW true
#define GxEPD2_290_T94_V2_IS_BW true
#define GxEPD2_290_BS_IS_BW true
#define GxEPD2_290_M06_IS_BW true
#define GxEPD2_290_GDEY029T94_IS_BW true
#define GxEPD2_290_GDEY029T71H_IS_BW true
#define GxEPD2_310_GDEQ031T10_IS_BW true
#define GxEPD2_371_IS_BW true
#define GxEPD2_370_TC1_IS_BW true
#define GxEPD2_420_IS_BW true
#define GxEPD2_420_M01_IS_BW true
#define GxEPD2_420_GDEY042T81_IS_BW true
#define GxEPD2_420_GYE042A87_IS_BW true
#define GxEPD2_420_SE0420NQ04_IS_BW true
#define GxEPD2_426_GDEQ0426T82_IS_BW true
#define GxEPD2_579_GDEY0579T93_IS_BW true
#define GxEPD2_583_IS_BW true
#define GxEPD2_583_T8_IS_BW true
#define GxEPD2_583_GDEQ0583T31_IS_BW true
#define GxEPD2_750_IS_BW true
#define GxEPD2_750_T7_IS_BW true
#define GxEPD2_750_GDEY075T7_IS_BW true
#define GxEPD2_1020_GDEM102T91_IS_BW true
#define GxEPD2_1085_GDEM1085T51_IS_BW true
#define GxEPD2_1160_T91_IS_BW true
#define GxEPD2_1248_IS_BW true
#define GxEPD2_1330_GDEM133T91_IS_BW true
#define GxEPD2_it60_IS_BW true
#define GxEPD2_it60_1448x1072_IS_BW true
#define GxEPD2_it78_1872x1404_IS_BW true
#define GxEPD2_it103_1872x1404_IS_BW true
// 3-color e-papers
#define GxEPD2_154c_IS_3C true
#define GxEPD2_154_Z90c_IS_3C true
#define GxEPD2_213c_IS_3C true
#define GxEPD2_213_Z19c_IS_3C true
#define GxEPD2_213_Z98c_IS_3C true
#define GxEPD2_266c_IS_3C true
#define GxEPD2_270c_IS_3C true
#define GxEPD2_290c_IS_3C true
#define GxEPD2_290_Z13c_IS_3C true
#define GxEPD2_290_C90c_IS_3C true
#define GxEPD2_420c_IS_3C true
#define GxEPD2_420c_Z21_IS_3C true
#define GxEPD2_420c_GDEY042Z98_IS_3C true
#define GxEPD2_579c_GDEY0579Z93_IS_3C true
#define GxEPD2_583c_IS_3C true
#define GxEPD2_583c_Z83_IS_3C true
#define GxEPD2_583c_GDEQ0583Z31_IS_3C true
#define GxEPD2_750c_IS_3C true
#define GxEPD2_750c_Z08_IS_3C true
#define GxEPD2_750c_Z90_IS_3C true
#define GxEPD2_1160c_GDEY116Z91_IS_3C true
#define GxEPD2_1248c_IS_3C true
#define GxEPD2_1330c_GDEM133Z91_IS_3C true
// 4-color e-paper
#define GxEPD2_213c_GDEY0213F51_IS_4C true
#define GxEPD2_266c_GDEY0266F51H_IS_4C true
#define GxEPD2_290c_GDEY029F51H_IS_4C true
#define GxEPD2_300c_IS_4C true
#define GxEPD2_420c_GDEY0420F51_IS_4C true
#define GxEPD2_437c_IS_4C true
#define GxEPD2_0579c_GDEY0579F51_IS_4C true
#define GxEPD2_1160c_GDEY116F51_IS_4C true
// 7-color e-paper
#define GxEPD2_565c_IS_7C true
#define GxEPD2_565c_GDEP0565D90_IS_7C true
#define GxEPD2_730c_GDEY073D46_IS_7C true
#define GxEPD2_730c_ACeP_730_IS_7C true
#define GxEPD2_730c_GDEP073E01_IS_7C true
#if defined(GxEPD2_DISPLAY_CLASS) && defined(GxEPD2_DRIVER_CLASS)
#define IS_GxEPD2_DRIVER(c, x) (c##x)
#define IS_GxEPD2_DRIVER_BW(x) IS_GxEPD2_DRIVER(x, _IS_BW)
#define IS_GxEPD2_DRIVER_3C(x) IS_GxEPD2_DRIVER(x, _IS_3C)
#define IS_GxEPD2_DRIVER_4C(x) IS_GxEPD2_DRIVER(x, _IS_4C)
#define IS_GxEPD2_DRIVER_7C(x) IS_GxEPD2_DRIVER(x, _IS_7C)
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_BW used with 3-color driver class"
#endif
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_BW used with 4-color driver class"
#endif
#if IS_GxEPD2_BW(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_BW used with 7-color driver class"
#endif
#if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_3C used with b/w driver class"
#endif
#if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_3C used with 4-color driver class"
#endif
#if IS_GxEPD2_3C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_3C used with 7-color driver class"
#endif
#if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_4C used with b/w driver class"
#endif
#if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_4C used with 3-color driver class"
#endif
#if IS_GxEPD2_4C(GxEPD2_DISPLAY_CLASS) && IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_4C used with 7-color driver class"
#endif
#if IS_GxEPD2_7C(GxEPD2_DISPLAY_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
#error "GxEPD2_7C used with less colors driver class"
#endif
#if !IS_GxEPD2_DRIVER_BW(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_3C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_4C(GxEPD2_DRIVER_CLASS) && !IS_GxEPD2_DRIVER_7C(GxEPD2_DRIVER_CLASS)
#error "neither BW nor 3C nor 4C nor 7C kind defined for driver class (error in GxEPD2_selection_check.h)"
#endif
#endif

49
include/ImageData.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef IMAGE_DATA_H
#define IMAGE_DATA_H
extern const unsigned char P000bw[];
extern const unsigned char P000yrw[];
extern const unsigned char P001bw[];
extern const unsigned char P001yrw[];
extern const unsigned char P002bw[];
extern const unsigned char P002yrw[];
extern const unsigned char P003bw[];
extern const unsigned char P003yrw[];
extern const unsigned char P004bw[];
extern const unsigned char P004yrw[];
extern const unsigned char P005bw[];
extern const unsigned char P005yrw[];
extern const unsigned char P006bw[];
extern const unsigned char P006yrw[];
extern const unsigned char P007bw[];
extern const unsigned char P007yrw[];
extern const unsigned char P008bw[];
extern const unsigned char P008yrw[];
extern const unsigned char P009bw[];
extern const unsigned char P009yrw[];
extern const unsigned char P010bw[];
extern const unsigned char P010yrw[];
extern const unsigned char P011bw[];
extern const unsigned char P011yrw[];
extern const unsigned char P012bw[];
extern const unsigned char P012yrw[];
extern const unsigned char P013bw[];
extern const unsigned char P013yrw[];
extern const unsigned char P014bw[];
extern const unsigned char P014yrw[];
extern const unsigned char P015bw[];
extern const unsigned char P015yrw[];
extern const unsigned char P016bw[];
extern const unsigned char P016yrw[];
extern const unsigned char P017bw[];
extern const unsigned char P017yrw[];
extern const unsigned char* black_images[];
extern const unsigned char* red_images[];
#define IMAGE_COUNT 18
#define IMAGE_WIDTH 240
#define IMAGE_HEIGHT 160
#endif // IMAGE_DATA_H

39
include/README Normal file
View File

@@ -0,0 +1,39 @@
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

22
include/_preference.h Normal file
View File

@@ -0,0 +1,22 @@
#ifndef ___PREFERENCE_H__
#define ___PREFERENCE_H__
#include <Preferences.h>
#define PREF_NAMESPACE "J_CALENDAR"
// Preferences KEY定义
// !!!preferences key限制15字符
#define PREF_SI_CAL_DATE "SI_CAL_DATE" // 屏幕当前显示的日期
#define PREF_SI_WEEK_1ST "SI_WEEK_1ST" // 每周第一天0: 周日默认1:周一
#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: 实时天气
#define PREF_QWEATHER_LOC "QWEATHER_LOC" // 地理位置
#define PREF_CD_DAY_DATE "CD_DAY_DATE" // 倒计日
#define PREF_CD_DAY_LABLE "CD_DAY_LABLE" // 倒计日名称
#define PREF_TAG_DAYS "TAG_DAYS" // tag day
// 假期信息tm年假期日(int8),假期日(int8)...
#define PREF_HOLIDAY "HOLIDAY"
#endif

15
include/_sntp.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef ___SNTP_H__
#define ___SNTP_H__
int _sntp_status();
void _sntp_exec(int status = 0);
typedef enum {
SYNC_STATUS_IDLE = -1,
SYNC_STATUS_IN_PROGRESS = 0,
SYNC_STATUS_OK = 1,
SYNC_STATUS_NOK = 2,
SYNC_STATUS_TOO_LATE = 3
} _sntp_status_t;
#endif

9
include/font.h Normal file
View File

@@ -0,0 +1,9 @@
#ifndef __FONT_H__
#define __FONT_H__
#include <stdbool.h>
#include <u8g2_fonts.h>
extern const uint8_t u8g2_font_qweather_icon_16[] U8G2_FONT_SECTION("u8g2_font_qweather_icon_16");
#endif // __FONT_H__

13
include/holiday.h Normal file
View File

@@ -0,0 +1,13 @@
#ifndef ___HOLIDAY_H__
#define ___HOLIDAY_H__
struct Holiday {
int year;
int month;
int holidays[50];
int length;
};
bool getHolidays(Holiday& result, int year, int month);
#endif

15
include/led.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef ___LED_H__
#define ___LED_H__
#include <Arduino.h>
#define PIN_LED GPIO_NUM_22
void led_init();
void led_fast();
void led_slow();
void led_on();
void led_off();
void led_config();
#endif

43
include/nongli.h Normal file
View File

@@ -0,0 +1,43 @@
// 根据日期计算农历信息
//
// Author: Jerry (jerry@jade-solution.com)
// Version: 1.0.0
//
// https://github.com/JADE-Jerry/nongli
//
// Copyright (C) 2024-2025 JADE Software Co., Ltd.
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
#ifndef __NONGLI_H__
#define __NONGLI_H__
extern const char* nl_jq_text[24];
extern const char* nl_tg_text[10]; // 天干
extern const char* nl_dz_text[12]; // 地支
extern const char* nl_sx_text[12]; // 生肖
void nl_month_days(int year, int month, int* result);
void nl_year_jq(int year, int* jqList);
int nl_tg(int year);
int nl_dz(int year);
#endif

12
include/ota.h Normal file
View File

@@ -0,0 +1,12 @@
#define HTTP_UE_TOO_LESS_SPACE (-100)
#define HTTP_UE_SERVER_NOT_REPORT_SIZE (-101)
#define HTTP_UE_SERVER_FILE_NOT_FOUND (-102)
#define HTTP_UE_SERVER_FORBIDDEN (-103)
#define HTTP_UE_SERVER_WRONG_HTTP_CODE (-104)
#define HTTP_UE_SERVER_FAULTY_MD5 (-105)
#define HTTP_UE_BIN_VERIFY_HEADER_FAILED (-106)
#define HTTP_UE_BIN_FOR_WRONG_FLASH (-107)
#define HTTP_UE_NO_PARTITION (-108)
void ota_update();

14
include/screen_ink.h Normal file
View File

@@ -0,0 +1,14 @@
int si_calendar_status();
void si_calendar();
int si_wifi_status();
void si_wifi();
int si_weather_status();
void si_weather();
int si_screen_status();
void si_screen();
void print_status();

3074
include/song_list.h Normal file

File diff suppressed because it is too large Load Diff

2597
include/tangshi_list.h Normal file

File diff suppressed because it is too large Load Diff

4
include/version.h Normal file
View File

@@ -0,0 +1,4 @@
#ifndef J_VERSION
#define J_VERSION "1.0.x-dev"
#endif

18
include/weather.h Normal file
View File

@@ -0,0 +1,18 @@
#ifndef __WEATHER_H__
#define __WEATHER_H__
#include <API.hpp>
int8_t weather_type();
int8_t weather_status();
Weather* weather_data_now();
DailyForecast* weather_data_daily();
void weather_exec(int status = 0);
void weather_stop();
Hitokoto* hitokoto_data();
int8_t hitokoto_status();
HourlyForecast* weather_data_hourly();
int8_t hourly_weather_status();
#endif

46
lib/README Normal file
View File

@@ -0,0 +1,46 @@
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

10
library.properties Normal file
View File

@@ -0,0 +1,10 @@
name=JCalendar
version=1.0.22
author=Jerry <jerry@jade-solution.com>
maintainer=Jerry <jerry@jade-solution.com>
sentence=墨水屏桌面日历
paragraph=桌面小摆件,显示日历及天气信息及附属功能。
architectures=esp32
url=https://github.com/JADE-Jerry/jcalendar
license=GNU General Public License v3.0
category=Display

7
min_spiffs.csv Normal file
View File

@@ -0,0 +1,7 @@
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x1E0000,
app1, app, ota_1, 0x1F0000,0x1E0000,
spiffs, data, spiffs, 0x3D0000,0x20000,
coredump, data, coredump,0x3F0000,0x10000,
1 # Name Type SubType Offset Size Flags
2 nvs data nvs 0x9000 0x5000
3 otadata data ota 0xe000 0x2000
4 app0 app ota_0 0x10000 0x1E0000
5 app1 app ota_1 0x1F0000 0x1E0000
6 spiffs data spiffs 0x3D0000 0x20000
7 coredump data coredump 0x3F0000 0x10000

82
platformio.ini Normal file
View File

@@ -0,0 +1,82 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
description = Ink Screen Calendar
default_envs =
750c
[common]
framework = arduino
lib_deps =
zinggjm/GxEPD2@^1.6.0
olikraus/U8g2_for_Adafruit_GFX@^1.8.0
bblanchon/ArduinoJson@^7.2.0
tzapu/WiFiManager@^2.0.17
mathertel/OneButton@^2.6.1
[env:z98]
build_type = release
platform = espressif32
board = esp32dev
framework = ${common.framework}
lib_deps =
${common.lib_deps}
nodaoli/ArduinoUZlib@^1.0.2
upload_speed = 460800
monitor_speed = 115200
board_build.partitions = min_spiffs.csv
build_flags =
-D SI_DRIVER=98
[env:z21]
build_type = release
platform = espressif32
board = esp32dev
framework = ${common.framework}
lib_deps =
${common.lib_deps}
nodaoli/ArduinoUZlib@^1.0.2
upload_speed = 460800
monitor_speed = 115200
board_build.partitions = min_spiffs.csv
build_flags =
-D SI_DRIVER=21
[env:750c]
build_type = release
platform = espressif32
board = esp32dev
framework = ${common.framework}
lib_deps =
${common.lib_deps}
nodaoli/ArduinoUZlib@^1.0.2
upload_speed = 460800
monitor_speed = 115200
board_build.partitions = min_spiffs.csv
build_flags =
-D SI_DRIVER=750
[env:esp32c3]
platform = espressif32
board = esp32-c3-devkitm-1
board_build.flash_mode = dio
framework = ${common.framework}
lib_deps =
${common.lib_deps}
nodaoli/ArduinoUZlib@^1.0.2
upload_speed = 460800
monitor_speed = 115200
board_build.partitions = min_spiffs.csv
build_flags =
-DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1

300
src/API.hpp Normal file
View File

@@ -0,0 +1,300 @@
#ifndef __API_HPP__
#define __API_HPP__
#include <Arduino.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <esp_http_client.h>
#include <ArduinoUZlib.h> // 解压gzip
struct Weather {
String time;
int8_t temp;
int8_t humidity;
int16_t wind360;
String windDir;
int8_t windScale;
uint8_t windSpeed;
uint16_t icon;
String text;
String updateTime;
};
struct DailyWeather {
String date;
String sunrise;
String sunset;
String moonPhase;
uint16_t moonPhaseIcon;
int8_t tempMax;
int8_t tempMin;
int8_t humidity;
uint16_t iconDay;
String textDay;
uint16_t iconNight;
String textNight;
int16_t wind360Day;
String windDirDay;
int8_t windScaleDay;
uint8_t windSpeedDay;
int16_t wind360Night;
String windDirNight;
int8_t windScaleNight;
uint8_t windSpeedNight;
};
struct HourlyForecast {
Weather* weather;
uint8_t length;
uint8_t interval;
};
struct DailyForecast {
DailyWeather* weather;
uint8_t length;
String updateTime;
};
struct Hitokoto {
String sentence;
String from;
String from_who;
};
struct Bilibili {
uint64_t follower;
uint64_t view;
uint64_t likes;
};
template<uint8_t MAX_RETRY = 3>
class API {
using callback = std::function<bool(JsonDocument&)>;
using precall = std::function<void()>;
private:
HTTPClient http;
WiFiClientSecure wifiClient;
bool getRestfulAPI(String url, callback cb, precall pre = precall()) {
// Serial.printf("Request Url: %s\n", url.c_str());
JsonDocument doc;
for (uint8_t i = 0; i < MAX_RETRY; i++) {
bool shouldRetry = false;
if (http.begin(wifiClient, url)) {
if (pre) pre();
// Serial.printf("Before GET\n");
int httpCode = http.GET();
// Serial.printf("GET %d\n", httpCode);
if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_NOT_MODIFIED) {
bool isGzip = false;
int headers = http.headers();
for (int j = 0; j < headers; j++) {
String headerName = http.headerName(j);
String headerValue = http.header(j);
// Serial.println(headerName + ": " + headerValue);
if (headerName.equalsIgnoreCase("Content-Encoding") && headerValue.equalsIgnoreCase("gzip")) {
isGzip = true;
break;
}
}
String s = http.getString();
DeserializationError error;
if (isGzip) {
// gzip解压缩
uint8_t* outBuf = NULL;
size_t outLen = 0;
ArduinoUZlib::decompress((uint8_t*)s.c_str(), (uint32_t)s.length(), outBuf, outLen);
error = deserializeJson(doc, (char*)outBuf, outLen);
} else {
error = deserializeJson(doc, s);
}
if (!error) {
wifiClient.flush();
http.end();
return cb(doc);
} else {
Serial.print(F("Parse JSON failed, error: "));
Serial.println(error.c_str());
shouldRetry = error == DeserializationError::IncompleteInput;
}
} else {
Serial.print(F("Get failed, error: "));
if (httpCode < 0) {
Serial.println(http.errorToString(httpCode));
shouldRetry = httpCode == HTTPC_ERROR_CONNECTION_REFUSED || httpCode == HTTPC_ERROR_CONNECTION_LOST || httpCode == HTTPC_ERROR_READ_TIMEOUT;
} else {
Serial.println(httpCode);
}
}
wifiClient.flush();
http.end();
} else {
Serial.println(F("Unable to connect"));
}
if (!shouldRetry) break;
Serial.println(F("Retry after 10 second"));
delay(5000);
}
return false;
}
public:
API() {
// http.setTimeout(10000);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
const char* encoding = "Content-Encoding";
const char* headerKeys[1] = {};
headerKeys[0] = encoding;
http.collectHeaders(headerKeys, 1);
wifiClient.setInsecure();
}
~API() {}
// 获取 HTTPClient
HTTPClient& httpClient() {
return http;
}
// 和风天气 - 实时天气: https://dev.qweather.com/docs/api/weather/weather-now/
bool getWeatherNow(Weather& result, const char* host, const char* key, const char* locid) {
// return getRestfulAPI("https://www.baidu.com", [&result](JsonDocument& json) {
return getRestfulAPI(
"https://" + String(host) + "/v7/weather/now?key=" + String(key) + "&location=" + String(locid), [&result](JsonDocument& json) {
if (strcmp(json["code"], "200") != 0) {
Serial.print(F("Get weather failed, error: "));
Serial.println(json["code"].as<const char*>());
return false;
}
result.updateTime = String(json["updateTime"].as<const char*>());
JsonObject now = json["now"];
result.time = now["obsTime"].as<const char*>();
result.temp = atoi(now["temp"]);
result.humidity = atoi(now["humidity"]);
result.wind360 = atoi(now["wind360"]);
result.windDir = now["windDir"].as<const char*>();
result.windScale = atoi(now["windScale"]);
result.windSpeed = atoi(now["windSpeed"]);
result.icon = atoi(now["icon"]);
result.text = now["text"].as<const char*>();
return true;
});
}
// 和风天气 - 逐小时天气预报: https://dev.qweather.com/docs/api/weather/weather-hourly-forecast/
bool getForecastHourly(HourlyForecast& result, const char* host, const char* key, const char* locid) {
return getRestfulAPI("https://" + String(host) + "/v7/weather/24h?key=" + String(key) + "&location=" + String(locid), [&result](JsonDocument& json) {
if (strcmp(json["code"], "200") != 0) {
Serial.print(F("Get hourly forecast failed, error: "));
Serial.println(json["code"].as<const char*>());
return false;
}
uint8_t i, hours = json["hourly"].size();
for (i = 0; i < result.length; i++) {
if (i * result.interval >= hours) break;
Weather& weather = result.weather[i];
JsonObject hourly = json["hourly"][i * result.interval];
weather.time = hourly["fxTime"].as<const char*>();
//weather.time = weather.time.substring(weather.time.length() - 11, weather.time.length()-6);
weather.temp = atoi(hourly["temp"]);
weather.humidity = atoi(hourly["humidity"]);
weather.wind360 = atoi(hourly["wind360"]);
weather.windDir = hourly["windDir"].as<const char*>();
weather.windScale = atoi(hourly["windScale"]);
weather.windSpeed = atoi(hourly["windSpeed"]);
weather.icon = atoi(hourly["icon"]);
weather.text = hourly["text"].as<const char*>();
}
result.length = i;
return true;
});
}
// 和风天气 - 逐天天气预报: https://dev.qweather.com/docs/api/weather/weather-daily-forecast/
bool getForecastDaily(DailyForecast& result, const char* host, const char* key, const char* locid) {
return getRestfulAPI("https://" + String(host) + "/v7/weather/3d?key=" + String(key) + "&location=" + String(locid), [&result](JsonDocument& json) {
if (strcmp(json["code"], "200") != 0) {
Serial.print(F("Get daily forecast failed, error: "));
Serial.println(json["code"].as<const char*>());
return false;
}
result.updateTime = String(json["updateTime"].as<const char*>());
uint8_t i;
for (i = 0; i < result.length; i++) {
DailyWeather& weather = result.weather[i];
JsonObject daily = json["daily"][i];
weather.date = daily["fxDate"].as<const char*>();
weather.sunrise = daily["sunrise"].as<const char*>();
weather.sunset = daily["sunset"].as<const char*>();
weather.moonPhase = daily["moonPhase"].as<const char*>();
weather.moonPhaseIcon = atoi(daily["moonPhaseIcon"]);
weather.tempMax = atoi(daily["tempMax"]);
weather.tempMin = atoi(daily["tempMin"]);
weather.humidity = atoi(daily["humidity"]);
weather.iconDay = atoi(daily["iconDay"]);
weather.textDay = daily["textDay"].as<const char*>();
weather.iconNight = atoi(daily["iconNight"]);
weather.textNight = daily["textNight"].as<const char*>();
weather.wind360Day = atoi(daily["wind360Day"]);
weather.windDirDay = daily["windDirDay"].as<const char*>();
weather.windScaleDay = atoi(daily["windScaleDay"]);
weather.windSpeedDay = atoi(daily["windSpeedDay"]);
weather.wind360Night = atoi(daily["wind360Night"]);
weather.windDirNight = daily["windDirNight"].as<const char*>();
weather.windScaleNight = atoi(daily["windScaleNight"]);
weather.windSpeedNight = atoi(daily["windSpeedNight"]);
}
result.length = i;
return true;
});
}
// 一言: https://developer.hitokoto.cn/sentence/
bool getHitokoto(Hitokoto& result) {
return getRestfulAPI("https://v1.hitokoto.cn/?max_length=25", [&result](JsonDocument& json) {
result.sentence = json["hitokoto"].as<const char*>();
result.from = json["from"].as<const char*>();
result.from_who = json["from_who"].as<const char*>();
return true;
});
}
// B站粉丝
bool getFollower(Bilibili& result, uint32_t uid) {
return getRestfulAPI("https://api.bilibili.com/x/relation/stat?vmid=" + String(uid), [&result](JsonDocument& json) {
if (json["code"] != 0) {
Serial.print(F("Get bilibili follower failed, error: "));
Serial.println(json["message"].as<const char*>());
return false;
}
result.follower = json["data"]["follower"];
return true;
});
}
// B站总播放量和点赞数
bool getLikes(Bilibili& result, uint32_t uid, const char* cookie) {
return getRestfulAPI(
"https://api.bilibili.com/x/space/upstat?mid=" + String(uid), [&result](JsonDocument& json) {
if (json["code"] != 0) {
Serial.print(F("Get bilibili likes failed, error: "));
Serial.println(json["message"].as<const char*>());
return false;
}
result.view = json["data"]["archive"]["view"];
result.likes = json["data"]["likes"];
return true;
},
[this, &cookie]() {
http.addHeader("Cookie", String("SESSDATA=") + cookie + ";");
});
}
};
#endif // __API_HPP__

189
src/ImageData.c Normal file

File diff suppressed because one or more lines are too long

0
src/_preference.cpp Normal file
View File

108
src/_sntp.cpp Normal file
View File

@@ -0,0 +1,108 @@
#include "_sntp.h"
#include <WiFi.h>
#include "esp_sntp.h"
#include "Arduino.h"
#include <_preference.h>
#include <API.hpp>
#include "holiday.h"
TaskHandle_t* _handler;
int _status = SYNC_STATUS_IDLE;
int _sntp_status() {
return _status;
}
/*
void time_sync_notification_cb(struct timeval *tv)
{
sntp_stop();
status = SYNC_STATUS_OK;
if (_handler != NULL)
{
vTaskDelete(_handler);
_handler == NULL;
}
}
*/
// SNTP 校准时间的任务
void _sntp_task(void* pvParameter) {
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, "ntp.aliyun.com");
// sntp_setservername(1, "pool.ntp.org");
// 设置时区
setenv("TZ", "CST-8", 1);
// sntp_set_time_sync_notification_cb(time_sync_notification_cb); // 回调函数
sntp_init();
unsigned long begin_millis = millis();
int c = 1;
int re;
while (1) {
if (sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
Serial.println("SNTP OK.");
re = SYNC_STATUS_OK;
break; // 获取时间成功,退出循环
}
if (millis() - begin_millis > 10 * 1000) { // 超时10s
Serial.println(F("SNTP timeout."));
re = SYNC_STATUS_NOK;
break;
}
c++;
vTaskDelay(pdMS_TO_TICKS(100));
}
time_t now = time(NULL);
struct tm tmInfo = { 0 };
localtime_r(&now, &tmInfo);
Serial.printf("Now: %d-%02d-%02d %02d:%02d:%02d\r\n", tmInfo.tm_year + 1900, tmInfo.tm_mon + 1, tmInfo.tm_mday, tmInfo.tm_hour, tmInfo.tm_min, tmInfo.tm_sec);
// 如果当前时间是23:50之后并且是定时器唤醒的情况下不作后续处理直接休眠待新的一天唤醒
if(tmInfo.tm_hour == 23 && tmInfo.tm_min > 50 && esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) {
re = SYNC_STATUS_TOO_LATE;
} else {
Holiday _holiday;
Preferences pref;
pref.begin(PREF_NAMESPACE);
size_t holiday_size = pref.getBytesLength(PREF_HOLIDAY);
if (holiday_size > 0) {
pref.getBytes(PREF_HOLIDAY, &_holiday, holiday_size);
}
pref.end();
// Serial.printf("堆总内存容量: %d\r\n", ESP.getHeapSize());
// Serial.printf("当前可用堆内存:%d\r\n", ESP.getFreeHeap());
// Serial.printf("最小剩余堆内存:%d\r\n", ESP.getMinFreeHeap());
if (_holiday.year != tmInfo.tm_year + 1900 || _holiday.month != tmInfo.tm_mon + 1) {
if (getHolidays(_holiday, tmInfo.tm_year + 1900, tmInfo.tm_mon + 1)) {
pref.begin(PREF_NAMESPACE);
pref.putBytes(PREF_HOLIDAY, &_holiday, sizeof(_holiday));
pref.end();
}
}
Serial.printf("堆总内存容量: %d\r\n", ESP.getHeapSize());
Serial.printf("当前可用堆内存:%d\r\n", ESP.getFreeHeap());
Serial.printf("最小剩余堆内存:%d\r\n", ESP.getMinFreeHeap());
}
_status = re;
vTaskDelete(NULL); // 删除任务
}
void _sntp_exec(int status) {
_status = status;
if (_status > 0) {
return;
}
if (!WiFi.isConnected()) {
_status = SYNC_STATUS_NOK;
}
_status = SYNC_STATUS_IN_PROGRESS;
// 创建一个SNTP 校准时间的任务
xTaskCreate(&_sntp_task, "_sntp_task", 1024 * 16, NULL, 6, _handler);
}

62
src/holiday.cpp Normal file
View File

@@ -0,0 +1,62 @@
#include "holiday.h"
#include "Arduino.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
// 获取当月假期信息。数组中数字为当月日期,正为休假,负为补工作日
// https://timor.tech/api/holiday/
bool getHolidays(Holiday& result, int year, int month) {
String req = "http://timor.tech/api/holiday/year/" + String(year) + "-" + String(month);
HTTPClient http;
http.setTimeout(10 * 1000);
http.begin(req);
Serial.printf("Request: %s\n", req.c_str());
int httpCode = http.GET();
if (httpCode != 200) {
http.end();
Serial.println(HTTPClient::errorToString(httpCode));
Serial.println("假日数据获取失败");
return false;
}
String resp = http.getString();
// Serial.printf("Response: %s\n", resp.c_str());
JsonDocument doc;
DeserializationError error = deserializeJson(doc, resp);
if (error) {
http.end();
Serial.print(F("Parse holiday data failed: "));
Serial.println(error.f_str());
return false;
}
http.end();
int status = doc["code"].as<int>();
if (status != 0) {
Serial.println("Get holidays error.");
return false;
}
result.year = year;
result.month = month;
JsonObject oHoliday = doc["holiday"].as<JsonObject>();
int i = 0;
Serial.printf("Holiday: ");
for (JsonPair kv : oHoliday) {
String key = String(kv.key().c_str());
JsonObject value = kv.value();
bool isHoliday = value["holiday"].as<bool>();
int day = key.substring(3, 5).toInt();
result.holidays[i] = day * (isHoliday ? 1 : -1); // 假期为正,补工作日为负
Serial.printf("%d ", result.holidays[i]);
i++;
if (i >= 50) break;
}
Serial.println();
result.length = i;
return true;
}

110
src/led.cpp Normal file
View File

@@ -0,0 +1,110 @@
#include "led.h"
#define PIN_LED GPIO_NUM_22
TaskHandle_t LED_HANDLER;
int8_t BLINK_TYPE;
void led_init()
{
pinMode(PIN_LED, OUTPUT);
}
void task_led(void *param)
{
while(1)
{
switch(BLINK_TYPE)
{
case 0:
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case 1:
digitalWrite(PIN_LED, LOW); // On
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case 2:
digitalWrite(PIN_LED, LOW); // On
vTaskDelay(pdMS_TO_TICKS(1000));
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
break;
case 3:
digitalWrite(PIN_LED, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(200));
break;
case 4:
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, LOW); // On
vTaskDelay(pdMS_TO_TICKS(200));
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
break;
default:
digitalWrite(PIN_LED, HIGH); // Off
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
}
void led_fast()
{
BLINK_TYPE = 3;
if (LED_HANDLER != NULL)
{
vTaskDelete(LED_HANDLER);
}
xTaskCreate(task_led, "TASK_LED", 2048, NULL, 5, &LED_HANDLER);
}
void led_slow()
{
BLINK_TYPE = 2;
if (LED_HANDLER != NULL)
{
vTaskDelete(LED_HANDLER);
}
xTaskCreate(task_led, "TASK_LED", 2048, NULL, 5, &LED_HANDLER);
}
void led_config()
{
BLINK_TYPE = 4;
if (LED_HANDLER != NULL)
{
vTaskDelete(LED_HANDLER);
}
xTaskCreate(task_led, "TASK_LED", 2048, NULL, 5, &LED_HANDLER);
}
void led_on()
{
BLINK_TYPE = 1;
if (LED_HANDLER != NULL)
{
vTaskDelete(LED_HANDLER);
}
xTaskCreate(task_led, "TASK_LED", 2048, NULL, 5, &LED_HANDLER);
}
void led_off()
{
BLINK_TYPE = 0;
if (LED_HANDLER != NULL)
{
vTaskDelete(LED_HANDLER);
}
xTaskCreate(task_led, "TASK_LED", 2048, NULL, 5, &LED_HANDLER);
}

359
src/main.cpp Normal file
View File

@@ -0,0 +1,359 @@
#include <Arduino.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>
#include "OneButton.h"
#include "led.h"
#include "_sntp.h"
#include "weather.h"
#include "screen_ink.h"
#include "_preference.h"
#include "version.h"
#define PIN_BUTTON GPIO_NUM_14 // 注意由于此按键负责唤醒因此需要选择支持RTC唤醒的PIN脚。
OneButton button(PIN_BUTTON, true);
void IRAM_ATTR checkTicks() {
button.tick();
}
WiFiManager wm;
WiFiManagerParameter para_qweather_host("qweather_host", "和风天气Host", "", 64); // 和风天气key
WiFiManagerParameter para_qweather_key("qweather_key", "和风天气API Key", "", 32); // 和风天气key
// const char* test_html = "<br/><label for='test'>天气模式</label><br/><input type='radio' name='test' value='0' checked> 每日天气test </input><input type='radio' name='test' value='1'> 实时天气test</input>";
// WiFiManagerParameter para_test(test_html);
WiFiManagerParameter para_qweather_type("qweather_type", "天气类型0:每日天气1:实时天气)", "0", 2, "pattern='\\[0-1]{1}'"); // 城市code
WiFiManagerParameter para_qweather_location("qweather_loc", "位置ID", "", 64); // 城市code
WiFiManagerParameter para_cd_day_label("cd_day_label", "倒数日4字以内", "", 10); // 倒数日
WiFiManagerParameter para_cd_day_date("cd_day_date", "日期yyyyMMdd", "", 8, "pattern='\\d{8}'"); // 城市code
WiFiManagerParameter para_tag_days("tag_days", "日期TagyyyyMMddx详见README", "", 30); // 日期Tag
WiFiManagerParameter para_si_week_1st("si_week_1st", "每周起始0:周日1:周一)", "0", 2, "pattern='\\[0-1]{1}'"); // 每周第一天
void print_wakeup_reason() {
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println(F("Wakeup caused by external signal using RTC_IO"));
break;
case ESP_SLEEP_WAKEUP_EXT1:
Serial.println(F("Wakeup caused by external signal using RTC_CNTL"));
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println(F("Wakeup caused by timer"));
break;
case ESP_SLEEP_WAKEUP_TOUCHPAD:
Serial.println(F("Wakeup caused by touchpad"));
break;
case ESP_SLEEP_WAKEUP_ULP:
Serial.println(F("Wakeup caused by ULP program"));
break;
default:
Serial.printf( "Wakeup was not caused by deep sleep.\n");
}
}
void buttonClick(void* oneButton);
void buttonDoubleClick(void* oneButton);
void buttonLongPressStop(void* oneButton);
void go_sleep();
unsigned long _idle_millis;
unsigned long TIME_TO_SLEEP = 180 * 1000;
bool _wifi_flag = false;
unsigned long _wifi_failed_millis;
void setup() {
delay(10);
Serial.begin(115200);
Serial.println(".");
print_wakeup_reason();
Serial.println("\r\n\r\n\r\n");
delay(10);
button.setClickMs(300);
button.setPressMs(3000); // 设置长按的时长
button.attachClick(buttonClick, &button);
button.attachDoubleClick(buttonDoubleClick, &button);
// button.attachMultiClick()
button.attachLongPressStop(buttonLongPressStop, &button);
attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), checkTicks, CHANGE);
Serial.printf("***********************\r\n");
Serial.printf(" J-Calendar\r\n");
Serial.printf(" version: %s\r\n", J_VERSION);
Serial.printf("***********************\r\n\r\n");
Serial.printf("Copyright © 2022-2025 JADE Software Co., Ltd. All Rights Reserved.\r\n\r\n");
led_init();
led_fast();
Serial.println(F("Wm begin..."));
wm.setHostname(F("J-Calendar"));
wm.setEnableConfigPortal(false);
wm.setConnectTimeout(10);
if (wm.autoConnect()) {
Serial.println(F("Connect OK."));
led_on();
_wifi_flag = true;
} else {
Serial.println(F("Connect failed."));
_wifi_flag = false;
_wifi_failed_millis = millis();
led_slow();
_sntp_exec(2);
weather_exec(2);
WiFi.mode(WIFI_OFF); // 提前关闭WIFI省电
Serial.println(F("Wifi closed."));
}
}
/**
* 处理各个任务
* 1. sntp同步
* 前置条件Wifi已连接
* 2. 刷新日历
* 前置条件sntp同步完成无论成功或失败
* 3. 刷新天气信息
* 前置条件wifi已连接
* 4. 系统配置
* 前置条件:无
* 5. 休眠
* 前置条件:所有任务都完成或失败,
*/
void loop() {
button.tick(); // 单击,刷新页面;双击,打开配置;长按,重启
wm.process();
// 前置任务wifi已连接
// sntp同步
if (_sntp_status() == -1) {
_sntp_exec();
}
// 如果是定时器唤醒并且接近午夜23:50之后则直接休眠
if (_sntp_status() == SYNC_STATUS_TOO_LATE) {
go_sleep();
}
// 前置任务wifi已连接
// 获取Weather信息
if (weather_status() == -1) {
weather_exec();
}
// 刷新日历
// 前置任务sntp、weather
// 执行条件:屏幕状态为待处理
if (_sntp_status() > 0 && weather_status() > 0 && si_screen_status() == -1) {
// 数据获取完毕后关闭Wifi省电
if (!wm.getConfigPortalActive()) {
WiFi.mode(WIFI_OFF);
}
Serial.println(F("Wifi closed after data fetch."));
si_screen();
}
// 休眠
// 前置条件:屏幕刷新完成(或成功)
// 未在配置状态,且屏幕刷新完成,进入休眠
if (!wm.getConfigPortalActive() && si_screen_status() > 0) {
if(_wifi_flag) {
go_sleep();
}
if(!_wifi_flag && millis() - _wifi_failed_millis > 10 * 1000) { // 如果wifi连接不成功等待10秒休眠
go_sleep();
}
}
// 配置状态下,
if (wm.getConfigPortalActive() && millis() - _idle_millis > TIME_TO_SLEEP) {
go_sleep();
}
delay(10);
}
// 刷新页面
void buttonClick(void* oneButton) {
Serial.println(F("Button click."));
if (wm.getConfigPortalActive()) {
Serial.println(F("In config status."));
} else {
Serial.println(F("Refresh screen manually."));
si_screen();
}
}
void saveParamsCallback() {
Preferences pref;
pref.begin(PREF_NAMESPACE);
pref.putString(PREF_QWEATHER_HOST, para_qweather_host.getValue());
pref.putString(PREF_QWEATHER_KEY, para_qweather_key.getValue());
pref.putString(PREF_QWEATHER_TYPE, strcmp(para_qweather_type.getValue(), "1") == 0 ? "1" : "0");
pref.putString(PREF_QWEATHER_LOC, para_qweather_location.getValue());
pref.putString(PREF_CD_DAY_LABLE, para_cd_day_label.getValue());
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.end();
Serial.println(F("Params saved."));
_idle_millis = millis(); // 刷新无操作时间点
ESP.restart();
}
void preSaveParamsCallback() {
}
// 双击打开配置页面
void buttonDoubleClick(void* oneButton) {
Serial.println(F("Button double click."));
if (wm.getConfigPortalActive()) {
ESP.restart();
return;
}
if (weather_status == 0) {
weather_stop();
}
// 设置配置页面
// 根据配置信息设置默认值
Preferences pref;
pref.begin(PREF_NAMESPACE);
String qHost = pref.getString(PREF_QWEATHER_HOST);
String qToken = pref.getString(PREF_QWEATHER_KEY);
String qType = pref.getString(PREF_QWEATHER_TYPE, "0");
String qLoc = pref.getString(PREF_QWEATHER_LOC);
String cddLabel = pref.getString(PREF_CD_DAY_LABLE);
String cddDate = pref.getString(PREF_CD_DAY_DATE);
String tagDays = pref.getString(PREF_TAG_DAYS);
String week1st = pref.getString(PREF_SI_WEEK_1ST, "0");
pref.end();
para_qweather_host.setValue(qHost.c_str(), 64);
para_qweather_key.setValue(qToken.c_str(), 32);
para_qweather_location.setValue(qLoc.c_str(), 64);
para_qweather_type.setValue(qType.c_str(), 1);
para_cd_day_label.setValue(cddLabel.c_str(), 16);
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);
wm.setTitle("J-Calendar");
wm.addParameter(&para_si_week_1st);
wm.addParameter(&para_qweather_host);
wm.addParameter(&para_qweather_key);
wm.addParameter(&para_qweather_type);
wm.addParameter(&para_qweather_location);
wm.addParameter(&para_cd_day_label);
wm.addParameter(&para_cd_day_date);
wm.addParameter(&para_tag_days);
// 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
wm.setConfigPortalBlocking(false);
wm.setBreakAfterConfig(true);
wm.setPreSaveParamsCallback(preSaveParamsCallback);
wm.setSaveParamsCallback(saveParamsCallback);
wm.setSaveConnect(false); // 保存完wifi信息后是否自动连接设置为否以便于用户继续配置param。
wm.startConfigPortal("J-Calendar", "password");
led_config(); // LED 进入三快闪状态
// 控制配置超时180秒后休眠
_idle_millis = millis();
}
// 重置系统,并重启
void buttonLongPressStop(void* oneButton) {
Serial.println("Button long press.");
// 删除Preferencesnamespace下所有健值对。
Preferences pref;
pref.begin(PREF_NAMESPACE);
pref.clear();
pref.end();
ESP.restart();
}
#define uS_TO_S_FACTOR 1000000
#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();
if (_qweather_key.length() == 0 || weather_type() == 0) { // 没有配置天气或者使用按日天气,则第二天刷新。
Serial.println(F("Sleep to two hours..."));
now += 3600 * 2;
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);
} else {
if (tmNow.tm_hour % 2 == 0) { // 将时间推后两个小时,偶整点刷新。
now += 7200;
} else {
now += 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);
}
esp_sleep_enable_timer_wakeup(p * (uint64_t)uS_TO_S_FACTOR);
esp_sleep_enable_ext0_wakeup(PIN_BUTTON, 0);
// 省电考虑关闭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_SLOW_MEM, ESP_PD_OPTION_OFF); //
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
// 省电考虑重置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电流
delay(10);
Serial.println(F("Deep sleep..."));
Serial.flush();
esp_deep_sleep_start();
}

169
src/nongli.cpp Normal file
View File

@@ -0,0 +1,169 @@
#include <nongli.h>
#include <math.h>
// 万年历字典
const int _LunarCalDic[] =
{
0x04AE53, 0x0A5748, 0x5526BD, 0x0D2650, 0x0D9544, 0x46AAB9, 0x056A4D, 0x09AD42, 0x24AEB6, 0x04AE4A, /*1901-1910*/
0x6A4DBE, 0x0A4D52, 0x0D2546, 0x5D52BA, 0x0B544E, 0x0D6A43, 0x296D37, 0x095B4B, 0x749BC1, 0x049754, /*1911-1920*/
0x0A4B48, 0x5B25BC, 0x06A550, 0x06D445, 0x4ADAB8, 0x02B64D, 0x095742, 0x2497B7, 0x04974A, 0x664B3E, /*1921-1930*/
0x0D4A51, 0x0EA546, 0x56D4BA, 0x05AD4E, 0x02B644, 0x393738, 0x092E4B, 0x7C96BF, 0x0C9553, 0x0D4A48, /*1931-1940*/
0x6DA53B, 0x0B554F, 0x056A45, 0x4AADB9, 0x025D4D, 0x092D42, 0x2C95B6, 0x0A954A, 0x7B4ABD, 0x06CA51, /*1941-1950*/
0x0B5546, 0x555ABB, 0x04DA4E, 0x0A5B43, 0x352BB8, 0x052B4C, 0x8A953F, 0x0E9552, 0x06AA48, 0x6AD53C, /*1951-1960*/
0x0AB54F, 0x04B645, 0x4A5739, 0x0A574D, 0x052642, 0x3E9335, 0x0D9549, 0x75AABE, 0x056A51, 0x096D46, /*1961-1970*/
0x54AEBB, 0x04AD4F, 0x0A4D43, 0x4D26B7, 0x0D254B, 0x8D52BF, 0x0B5452, 0x0B6A47, 0x696D3C, 0x095B50, /*1971-1980*/
0x049B45, 0x4A4BB9, 0x0A4B4D, 0xAB25C2, 0x06A554, 0x06D449, 0x6ADA3D, 0x0AB651, 0x093746, 0x5497BB, /*1981-1990*/
0x04974F, 0x064B44, 0x36A537, 0x0EA54A, 0x86B2BF, 0x05AC53, 0x0AB647, 0x5936BC, 0x092E50, 0x0C9645, /*1991-2000*/
0x4D4AB8, 0x0D4A4C, 0x0DA541, 0x25AAB6, 0x056A49, 0x7AADBD, 0x025D52, 0x092D47, 0x5C95BA, 0x0A954E, /*2001-2010*/
0x0B4A43, 0x4B5537, 0x0AD54A, 0x955ABF, 0x04BA53, 0x0A5B48, 0x652BBC, 0x052B50, 0x0A9345, 0x474AB9, /*2011-2020*/
0x06AA4C, 0x0AD541, 0x24DAB6, 0x04B64A, 0x6A573D, 0x0A4E51, 0x0D2646, 0x5E933A, 0x0D534D, 0x05AA43, /*2021-2030*/
0x36B537, 0x096D4B, 0xB4AEBF, 0x04AD53, 0x0A4D48, 0x6D25BC, 0x0D254F, 0x0D5244, 0x5DAA38, 0x0B5A4C, /*2031-2040*/
0x056D41, 0x24ADB6, 0x049B4A, 0x7A4BBE, 0x0A4B51, 0x0AA546, 0x5B52BA, 0x06D24E, 0x0ADA42, 0x355B37, /*2041-2050*/
0x09374B, 0x8497C1, 0x049753, 0x064B48, 0x66A53C, 0x0EA54F, 0x06B244, 0x4AB638, 0x0AAE4C, 0x092E42, /*2051-2060*/
0x3C9735, 0x0C9649, 0x7D4ABD, 0x0D4A51, 0x0DA545, 0x55AABA, 0x056A4E, 0x0A6D43, 0x452EB7, 0x052D4B, /*2061-2070*/
0x8A95BF, 0x0A9553, 0x0B4A47, 0x6B553B, 0x0AD54F, 0x055A45, 0x4A5D38, 0x0A5B4C, 0x052B42, 0x3A93B6, /*2071-2080*/
0x069349, 0x7729BD, 0x06AA51, 0x0AD546, 0x54DABA, 0x04B64E, 0x0A5743, 0x452738, 0x0D264A, 0x8E933E, /*2081-2090*/
0x0D5252, 0x0DAA47, 0x66B53B, 0x056D4F, 0x04AE45, 0x4A4EB9, 0x0A4D4C, 0x0D1541, 0x2D92B5 /*2091-2099*/
};
/**
* 根据当前公历月份计算每日的农历,以整形数数组返回
* 十进制高两位:农历月份
* 十进制低两位:农历日期
*/
void nl_month_days(int year, int month, int* result){
if (year < 1900 || year > 2099 || month < 1 || month > 12) {
return;
}
int daysofmonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (year % 4 == 0 && year % 100 != 0) {
daysofmonth[1] = 29;
}
int lunarInfo = _LunarCalDic[year - 1901];
int newYearDay = lunarInfo & 0x0000001F; // 春节公历日期
int newYearMonth = (lunarInfo >> 5) & 0x00000003; // 春节公历月份
int leapMonth = (lunarInfo >> 20) & 0x0000000F; // 闰月
if (newYearMonth > month) {
// 还未到春节,需要根据前一年的日历数据计算(情况仅为一种:当前为一月份,而春节为二月份)
int daysCount = newYearDay - 1 + daysofmonth[0];
int lastLunarInfo = _LunarCalDic[year - 2];
int lastLeapMonth = (lastLunarInfo >> 20) & 0x0000000F; // 闰月
int isLargeMonthLast12 = (lastLunarInfo >> (7 + (lastLeapMonth > 0 ? 0 : 1))) & 0x1;
int isLargeMonthLast11 = (lastLunarInfo >> (8 + (lastLeapMonth > 0 ? 0 : 1))) & 0x1;
int begin = 29 + isLargeMonthLast12 + 29 + isLargeMonthLast11 - daysCount + 1;
for (int i = 0; i < daysofmonth[0]; i++) {
if (begin + i <= 29 + isLargeMonthLast11) {
result[i] = 1100 + begin + i;
} else {
result[i] = 1200 + i + begin - 29 - isLargeMonthLast11;
}
}
} else if (newYearMonth == month) {
// 当月春节
int lastLunarInfo = _LunarCalDic[year - 2];
int lastLeapMonth = (lastLunarInfo >> 20) & 0x0000000F; // 闰月
int isLastLargeMonth = (lastLunarInfo >> (7 + (lastLeapMonth > 0 ? 0 : 1))) & 0x1;
int lastLunarDayInMonth = 29 + isLastLargeMonth;
// 新年的农历
for (int i = 0; i <= daysofmonth[newYearMonth - 1] - newYearDay; i++) {
result[newYearDay + i - 1] = 100 + i + 1;
}
// 陈年的农历
for (int i = 0; i < newYearDay - 1; i ++) {
result[i] = 1200 + lastLunarDayInMonth - newYearDay + 2 + i;
}
} else {
// 已过春节月份
// 当前月份1号与春节的间隔天数
int daysCount = -newYearDay + 1;
for (int i = newYearMonth; i < month; i++) {
daysCount += daysofmonth[i - 1];
}
int lunarMonth = 0;
int isLargeMonth = 0;
int lunarDayInMonth = 0;
while (1) {
isLargeMonth = (lunarInfo >> (19 - lunarMonth)) & 0x1;
lunarDayInMonth = 29 + isLargeMonth;
if (daysCount >= lunarDayInMonth) {
daysCount -= lunarDayInMonth;
lunarMonth++;
} else {
break;
}
}
// Serial.printf("lunarDayInMonth: %d\n", lunarDayInMonth);
for (int i = 0; i < daysofmonth[month - 1]; i++) {
if (daysCount >= lunarDayInMonth) { // 进入下一个月
daysCount -= lunarDayInMonth;
lunarMonth++;
}
// lunarMonth + 1 为当前月的计数月份
if (leapMonth == 0 || leapMonth >= lunarMonth + 1) {
result[i] = (lunarMonth + 1) * 100 + daysCount + 1; // 无闰月或闰月未到
} else if (leapMonth == lunarMonth) {
result[i] = lunarMonth * -100 + daysCount + 1; // 当月闰月
} else {
result[i] = lunarMonth * 100 + daysCount + 1; // 已过闰月
}
daysCount++;
}
}
}
const char* nl_jq_text[24] = {
"小寒", "大寒", "立春", "雨水", "惊蛰", "春分",
"清明", "谷雨", "立夏", "小满", "芒种", "夏至",
"小暑", "大暑", "立秋", "处暑", "白露", "秋分",
"寒露", "霜降", "立冬", "小雪", "大雪", "冬至"
};
/**
* 节气计算, 返回节气在本年的日期
*/
void nl_year_jq(int year, int* jqList) {
if (year < 1900 || year > 2099) {
return;
}
year = year - 1900; // 计算从1900年开始的积日数
int d = year / 4;
int m = year % 4;
int jr = (m == 0)? (1461 * d - 1) : (1461 * d + 365 * m); // 当年第一天的积日数
for(int i = 0; i < 24; i ++) {
float x = 365.242 * (year) + 6.2 + 15.22 * i - 1.9 * sin(0.262*i);
jqList[i] = (int)(x - jr);
}
}
const char* nl_tg_text[] = { "", "", "", "", "", "", "", "", "", ""}; // 天干
const char* nl_dz_text[] = { "", "", "", "", "", "", "", "", "", "", "", "" }; // 地支
const char* nl_sx_text[] = { "", "", "", "", "", "", "", "", "", "", "", "" }; // 生肖
/**
* 天干计算
* 输入:年份
* 输出天干index通过tg_str获取天干文字
* 注意:实际使用时,需要考虑当天是春节前还是春节后,农历以大年初一为起点
*/
int nl_tg(int year) {
return year % 10;
}
/**
* 地支计算(与生肖同)
* 输入:年份
* 输出地支index通过dz_str获取地支文字
* 注意:实际使用时,需要考虑当天是春节前还是春节后,农历以大年初一为起点
*/
int nl_dz(int year) {
return year % 12;
}

68
src/ota.cpp Normal file
View File

@@ -0,0 +1,68 @@
#include "ota.h"
#include <Arduino.h>
#include <HTTPUpdate.h>
String _ota_url = "http://bin.bemfa.com/b/=esp32bin.bin";
//当升级开始时,打印日志
void update_started() {
Serial.println("OTA: HTTP update process started");
}
//当升级结束时,打印日志
void update_finished() {
Serial.println("OTA: HTTP update process finished");
}
//当升级中,打印日志
void update_progress(int cur, int total) {
Serial.printf("OTA: HTTP update process at %d of %d bytes...\n", cur, total);
}
//当升级失败时,打印日志
void update_error(int err) {
Serial.printf("OTA: HTTP update fatal error code %d\n", err);
}
/**
* 固件升级
* 通过http请求获取远程固件实现升级
*/
void ota_update() {
Serial.println("start update");
if (!WiFi.isConnected()) {
WiFi.begin();
}
int i = 0;
while(WiFi.status() != WL_CONNECTED) {
if(i ++ > 5) {
break;
}
delay(1000);
}
if ((WiFi.status() == WL_CONNECTED)) {
Serial.println("start update");
WiFiClient UpdateClient;
httpUpdate.onStart(update_started);//当升级开始时
httpUpdate.onEnd(update_finished);//当升级结束时
httpUpdate.onProgress(update_progress);//当升级中
httpUpdate.onError(update_error);//当升级失败时
t_httpUpdate_return ret = httpUpdate.update(UpdateClient, _ota_url);
switch (ret) {
case HTTP_UPDATE_FAILED: //当升级失败
Serial.println("[update] Update failed.");
break;
case HTTP_UPDATE_NO_UPDATES: //当无升级
Serial.println("[update] No update.");
break;
case HTTP_UPDATE_OK: //当升级成功
Serial.println("[update] Update ok.");
break;
}
} else {
Serial.println("[update] Update failed due to no wifi.");
}
}

1076
src/screen_ink.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,227 @@
/*
Fontname: -FreeType-qweather icons-Medium-R-Normal--22-160-100-100-P-208-ISO10646-1
Copyright: QWeather
Glyphs: 132/233
BBX Build Mode: 0
*/
#include "font.h"
#ifdef U8G2_USE_LARGE_FONTS
const uint8_t u8g2_font_qweather_icon_16[6862] U8G2_FONT_SECTION("u8g2_font_qweather_icon_16") =
"\204\0\5\4\5\5\4\4\6\27\30\0\377\0\0\0\0\0\0\0\0\0\2\0\0\0\4\377\377\361\1\67"
"\326\42\332+\324\250\5\27\216X\342\10a\204X\202\210&NX\2\11\25\225!\202\12b\224\240bJ"
" \261\204\21M\20\261\204\60B \261\302\21-\270P#\5\0\361\2@\366\342\331'\324P\302\13D"
"L\241\4!M\210\343\4\21M\30\21\6\22\62\4\61\12\32A\204\61\6\23G \222\10\21O\10\21"
"E\10\63\204\60C\10\63\4\21\205\60\307\20S\214:q$\0\361\3\77\326\342\331+\324\250\211\26\216"
"X\2\5a\204Xb\214&\214X\2\11\25\222PQ\31\42\250 F\11*(QE!L\210!D"
"\12\203\224Q\304\20I\14A\302\12I\250\240\6!\216\24\0\361\4\77\326\342\331+\324\250\5\27\214`"
"\342\210A\206X\250\215\42\230\70a\11$\224H\241\14!R\10\244\210\24VHa\211a\32!\245\20"
"&\6qa\204\30F\20\241\211Q\304\20\6\221@\14\0\361\5\60V\242\332\252@\61\4\33g \261"
"\204\21\302\24Q\202\20d\220p\304\11$\214\202\312\30n\4!\7\225\350\230!\4\31\206\61&!H"
"\22\0\361\6,\61\256\336E\270\301H\23!\60!\304\12C(A\202\22D$QB\22E q\204"
"\21\350\4\301D\20K\14\221D\231\320)\0\361\7\66\266\42\332-P\61\207\24!\310 D\14B\304"
"\60D\25\255\220b\306\30E\30i\10B\20!\303\211\21\242\10B\206 d\20!\12!` \306\24"
"u\342P\0\361\10;\264\346\331)\304\1E\10O\210\360\302\10N\214\340\2\21-\224\320B\21,\30"
"\261\302\31$JF\12R\220\30D\10#\4)C\214!\222\10\202\204\25\216P\1\21\62XY\0\361"
"\11\77\267\42\336JL\62E\10R\210 \303\20P\14\1\5\21O\20\361D\31M\30B\302\11\350\34"
"\301\202\62E\244!\306\20h\224b\10\23\204\270\60D\14C\210\320\4)B\220\222H(\10\0\361\12"
":\324\342\331'\314\340\2\11#(aB\63N\214\222\204\230\312\30\302\210S\316 \203\215 `\10\2"
"\206 `\10\342\11a\214\31\206\230d\240\370\200\4\23\326 C\15\62\10\0\361\13<\324\42\332'\314"
"\340\302\21J\10!B+N\220b\310\30b,a\2\42h\214\321\204\20\60\204\20C\10\61\4\361\204"
"\60\246\220\267\12\23R\210\60D\32\42\14\221F\30B\264\201\0\361\14\66\264f\332\307\264!\306\22F"
"\34\202\310\20N\4\1\207\224\344\200\42\10\21P\20b\30b\222\371\240\14\26\210 \341\210Q\204\60C"
"\20\61\214(\201\10\26fP\0\361\15\66\224f\332\307\264!\306\22F\34\202\310\20N\4\1\207\224d"
"\10\1\212`\214!F\30e\266\70\303\20\21\210 A\210\61D\21\302\214P\304\60\242\4\42XP\0"
"\361\16\67\265f\332\307\270!\6\23F \212\10'\204\200\42\4\31B\220C\206 \240\20\306\30r\304"
"\10C\231\17\316h\201\210\22\316\20E\14\63B\31\342\210\22f\240a\1\361\17+U\42\333\250<\61"
"\6\23F j\14'D\210D\16\31B\220!\10(\204\61\6!(>\60Q\13&\260A\306\32d"
"\24\0\361\20\61\224\346\332\307\264!\306\22F\34\202\310\20N\4\1\207\224\344\200\42\30c\210\10#\220"
" TaA\5\24T\70\3\15\63F \303\10\42\342\224\0\361\21\66\225\342\332\250\270\61\306\22G\34"
"\222\310\30N\4\21\211\234\242\10\342\211a\312)A\214\60\255\342\305\10'\14!\204\30e\10a\206\10"
"b(\61\204\20nL\221\0\361\22N\225\342\326\247\70\42F\42\207\24\21\4\12A\220\360\204\20\213\20"
"\241\204\20C\240A\206\20F\244 \212\10*\14\21J\12(\4\62\310\12\201\204\220\303\10'\214@\202"
"\20G\210@\202\30\42\210!\2\11B\14!\204\10$\230a\2\23\11\0\361\23+U\42\333\250<\61"
"\6\23F j\14'D\210D\16\31B\220!\10\30\206\61%\235(> \61\25H\224\2\211^ "
"\301\0\361\24\77\226\346\336\310\274!F\23F\240\202\10\31n\4!C\20\222\222A\10(\304\61\246\214"
"\200\302`d\5\30\306\30\301\204!\306\20\203\14!\320\10A\14\25J\60\341\10\62\212\60\202\214\42\6"
"\0\361\25>\225\346\332\250\270\61\306\32F\234\202\310\20n\4\21C\10S\232#\6!FX\202\230a"
"\226\71a\12\21Z\20\42\210\21L\10B\210\60D\60!\214\20F\24\304\10-\204!\302\23\12\0\361"
"\26>\225\346\332\310\264\61\306\32F\234\202\310\20O\4\21\307\224\346\210\42\214 \216\31H\230eN\230"
"B\204\26\204\20a\4\23D\20B\204\21D\30AD#\22a\4\21T\20a\4\30\24\0\361\27\64"
"\225\242\332\250\270\61\6\13G\234\202\310\30N\210\20\211\234\242\10\2\206aLI'\5\63L\30\303\211"
"\21\235\60\304\10b\30\261\302\21+\14\61\305\14\12\0\361\30/\225\346\336\250\270\61\306\22G\34\222\310"
"\20O\4\21\307\24s\310\20B\14\243\234\202\20\34\37$\61\3\11I\14)\211!\306h\201\206\5\0"
"\361\31\66\226\342\332\310\270\61H\42\250\214\341\206\10Q\4!))B\210B\210\21P\20c\34r\322"
"\371\0\206&F\20\302\10\22\206\20\302\210!\306pb\210\27jh\0\361\32\71\226\342\336\310\274\61D"
"\23F\240j\14\67D\220d\216\31B\220\42\210\30\206\61&\241\70x \302\210\21\304\30B\204!F"
"\20\303\14\21\206\20\303\14\21\306p\202\6\6\0\361\33=\225\242\332\250\270\61\6\13G\234\202\310\30N"
"\210\20\211\34\62\204\20E\20\60\14SL:p\354@\304\10\42\214 \306\20B\210\60\202\30'\214 "
"\204\30'\10!\2\23\42\214\0\303\2\361\34A\225\242\332\250\270\61\6\13G\234\202\310\30N\210\20\211"
"\34\62\204\20E\20\60\14SL:p\354@\304\10\42\214 \306\20B\210\60\202\30'\214 \204\30'"
"\10!\2\23\42\214@\2\21%\240 \0\361\35\70\324\346\331.\304\1\11\14A\314\300\212\20j\210!"
"H\21&\20!\310!!\210\301\206\10\60\4\1C\20\220<!L\71\303\20\203\16\24\37\220`\302\32"
"d\250AF\1\361\36=\324&\332-H\21G\14A\314\300\212\20J\14!J\221\210\20\344\20\42\232"
"\20\2\206\20b\10!\206 \236\20\246\230\42\302L\225\26NPb\204!\322\20A\214$\304\20\242\215"
"\4\0\361\37CU\346\332%\314!C\20P\210\0\303\20N\220\340B\21L\30\261\302\21&\14\201B"
"\31\42$AB\10\42\250\60D\20!\250\60\242\20\222\20b\20$D(!\10#F(A\10\42\15"
"\61\214)\1\0\361 -u\346\332\250\270\61\306\22G\34\222\310\20n\4\1E\20qH\22E\10P"
"\214r\12Bp|\240\203\11l\14\262\306 ,\230p\0\361!\61\225\242\332\250\270\61\6\13G\234\202"
"\310\30N\210\20\211\34\62\204 C\20\60\14cJB\217\264\220B\32g$\201\4\32#\214\321\206\34"
"\63(\0\361\42\77\227\242\336\251\300\61F\23G\244\202H\31N\14\21\205\20\62\10!\203\20Q\14\361"
"\306\70\345\230\21N %\230r\202\30\360\210\220\302\30\42\214\220\2\11f\214\60\306\12c\214\0\205\15"
"\13\0\361#A\327\42\336\251D\61\206\23F\244\202H\31N\14\21\205\20\62\10!\203\20Q\14\1\305"
"\70\345\230\21P\20&\230rB p\204q\2\32\42\214\60\306\10$\230!&\65\316X\201Dp\320"
"Q\303\2\361$\61\225\342\332\250<\61\6\23F j\14'D\210D\16\31B\220!\10\30\206\61%"
"\35\70>`\301\205\22BX\202\14\66\306`\203\204\20d\70\0\361%\65\225\346\332\250\270\61\4\23F"
" \212\214\66\202\200\42\4\71$\211A\4(\206)E\231\17Px\201\204\21V !\220\64\204\244\206"
"\10\242 \61\2\11/,\0\361&:\324\342\331'\314\340\2\11#(a\4\63N\14\242\204\230\312\30"
"\302\210S\14!\242\11!`\10\2\206 `\10\342\11a\214\31\206\230d\240\250\241\5\62\326\30c\15"
"\22\14\0\361'=\324\342\331&\314\360\302\10$(aB\63.\214\242\302\20c\224\61\204\11\210\34B"
"D\23B\300\20B\14!\304\20\304\23\302\30#\16\61\311@\361B\11\213\214\241\310\30*\204P\202\1"
"\361(\60\225\242\332\250<\61\6\23F\234\202\310\30N\210\20\211\234d\10\2\12a\214A\7\216\17\222"
"\30A\5\42FPb\4\42R \221\23\63,\0\361)\62\224\246\332\250\264\61\206\22F\34\202\310\20"
"N\4\1\207\224\344\200A\30cJ\10\310\21\35\222\30a\210\61\206\20c\204\61F\30\3\5\22\65!"
"\3\3\361*<\265\246\336\310\270!\6\23F j\14'\204\200\42\4\71d\10!\212 `\30\306\224"
"\204\36)\201\4G\302\60a\204!\304\20a\4\61D\70C\4\61\324\10b\4\26D\240\321\2\361+"
"\71\324\346\331.\304\1\11\14AL\261\214\10j\210!H\21F\14!\10*b\64!\2\14A@\352"
"\215`\214\31\206\30\23\302\21\201\215\32Z\30E\215\61\326 !\204\2\0\361,;\324\352\335-H\21"
"G\14A\314\300\312\10i\214!H\11F\20!\6\42D\64!\4\14!\304\20B\14A<\21\214\71"
"\302\24\203\316\33.\230\260\306 K\220\241\4\11!\34\0\361-Er\256\336(\70\21B\20\254\244@"
"\306\20E\220@\304 $\20\62F(\201\210\21\306\30B\230@\304\12%\254P\202\31a\214\21R("
"\201\214\61\4!A\204@\2!C\214AB\11\243\264\20B\20.$\0\361.#\226a\333\307\274!"
"F\23F$\202H\11P\210 \203\10\347\311 \4\24b\254 \4\61\246(\204\0\361/(U\346\332"
"\310\270\61\4\23F \212\10'\204\200\42\4\31B\220!\4\31\202\200B\30c\20\202\342\203\341>p"
"\356\0\361\60%\66\341\333\242\240\62\244\62\206\10\302\10!\314@\344\214$!\202\206\21B\30\21\304\30"
"d\14\61\12*\2\0\361\61\63r\256\336MDAD\21%DqB\32%\214P\302\22C\20\221D"
"\11i\60\22\202X$@Q\2\21c\264\21BIB\224\201\2\14H\24\301\210\1\361\62(\261\251\333"
"(\310\360\306\20%\214\61B\31G\24!\304\20D\22\242\210#\204H\203\204R\316\61\202\205\60\226\20"
"a\0\361\63+s\352\332\306\254\61F\22G\240\220\2\12\62H!\303\12\342\201\21B|@\204\340D"
"\10PD!C\24f\34\221R\33\10\0\361\64-sj\332g\270\263D\21I\240H\206%\304\3#"
"\204\370\200\10\341\14#\24\221B\10\361\300\10!>@\336\60!\5$\314P\307\0\361\65%\225\246\332"
"\250\270\61\306\22G\34\222\310\30m\4\21iQ\4\361\304\60\305\244\23\305\7\304a\367\1u\4\0\361"
"\66,\227\242\336\310\300!\206\23F(\202H\31n\214 \203\20\347\201 \303\10Q\14\61\2\12B\24"
"c\214B\37t\307\335\7\332\25\0\361\67<V\242\332C\60Q\306\32&\264\300I\42D\210Q\206\30"
"A\24\61\204\11!\34\21\304\31h$\201F\22G\4qH\21C\30\21\304\20e\14\61\12*;\264"
"`\306\32F\60\61\0\361\70DV\242\332C\60QF\21d\230PF\11.\64\222\10\21b\224!F"
"\20E\14aB\10G\4q\6\32I\240\221\304\21A\34R\304\20F\4\61D\31C\214\202\12\13."
"\224Q\202\31E\220a\4\23\3\0\361\71DV\242\332A<\61\306\20D\214\61\2\31b\220\240\2\11"
"\252\240\62\244\62\304\10\302\10!L\10\1\221\63\222\34\42\207\30!\204\21A\214A\306\20\243\240\242\2"
"\11*\220!\6\11c\14A\304\30B<!\0\361:)\227\242\336\310\300!\206\23F(\202H\31/"
"\220\240\232\14B\310\60\202iC\300P\214)\13I\361Ar\334}\240]\1\361;,\226\242\332\310\270"
"\61\6\23G \222\10\31N\10\221\232\14A\310 \202iB<Q\314\70\313|\240IX\231\5\362\301"
"$a\15\0\361<*\326b\332\7\61\226\334y \224\7\306x\240\210\7\212x\340\377\201\21\36\60\341"
"\201\42\36(\343\201Q\36\10\307%\326\14\2\361=:\366\42\332\310\70BF:&\234dBQ'\220"
"u\202h&\210fBp\347\3\301<\20\314\3\301\274\363N\10\355\204\300N\30\353\204\241N(\350\4"
"T\214P\243\210W\20\0\361>\61\326\42\332\310\270\62F:E\34tBI(\14\205\204X(\4\206"
"B`\251_\12a\241 T\12#!\61\22\12\5\235pN\21\212\220\321\14\2\361\77\61\326\42\332\7"
"\61BH\42H\234\222D)K\14\303\204(.\210\342\220;\320\6\217CN\204\342\202\60L\214\262D"
")I\34\202D\42e\60t\0\361@\60\326\42\332\7\61\62J\22J\34\301D\21N\14\1\205\10\62"
"\4!\307\34Tn\22)B\220A\10(Fx\202\210&\314P\42\15\63\30:\0\361A\60\326\42\332"
"\347\64BH\22\210\34\221J\21\253\14\301\214\10\256\4\341J\10\357\336C\256\210\340\212\20\314\214\300\12"
"\21\252\230\201H\32\205\60t\0\361B/\326b\332\7\261!N\22\4\31aR\11G\15\201\224\10I"
"\5\221T\10\212\237jI\211\220\224\20g\15aT\21%\35APZ\316 \0\361C<\327b\336\7"
"\65Q\212\12\6\235`T\11g\215p\330\10\247\211`\134\10\307\205p\36\10\347\201p\36\10\307\205p"
"\234\10\247\211p\332\10\206\225`\224\11'\35a\216\22\205\270\203\0\361DF\317\66\336d,\21\4\11"
"%\214@B\11#\14\62\42\22J\10\221\12!R!D*\204H\205\20\251\20\42\25B\224D\210\220"
"\20!\210\23F\20\302\4\61D(B\14\21\212\30\221\11'\30Q\304\21C\244r\0\361EC\317\66"
"\336d,\21\204\12#\250\60\302 #Ra\4\25FPa\4\25FPa\4\25FPa\4\25B"
"\224D\10A\34!\202\20&\210!B\21b\210P\204\30\42\230p\202\21E\34\61D*\7\0\361F"
"-\63\245\333+\34\61$\61\11\61\302\30b\10\61\242\20\4\11BHA\12D\204!\202\24\210\10\243"
"\4!F\20B\14\62D\10C\2\361\254,\326\42\332+\324\250\5\27\216X\2\5a\204X\250\251\305"
"\24+C\60\61\12S\334R\15-!\214\20H\254pD\13.\324H\1\361\255;\326\42\332&TQ"
"\202\23D\320\240\304(M\4\61F\23E\10\221\302))\324\60\14\32!\204\324Tz \220\7\310x"
"\240\204\7Lx\340\201&\36(\342\1B\36\20\313 \0\361\256\65\326\342\331+\324\250\5\27\214`\342"
"\210A\206`\307%\266\24W\206`b\24\246\320+\242,!H\20\215\224Q\304\20I\14A\302\12I"
"\250\240\310 \256\20\0\361\257\66\326\342\331+\324\250\5\27\214`\342\210A\206X\250\251\305\24W\206`"
"\201\24$\304*\62\14\343\6)\247$A\206\13#\304\60\202\10M\214\42\306(\210\204R\0\361\260\60"
"Vb\332\252\274\61\304\42\206\234!H\21&\10D\304QC\20\21T\20!\214\7\306x\240\204\7\376"
"\3%<P\306\3\303\210\200\202`\6\1\361\261\34\61.\333D\270\321\10\253\255\243\216B)\241e\36"
"\20\341\211\67ZY\310\30\0\361\262\65\226\42\332-P\61\207\24!\310 D\14B\304\60\304\33\203\250"
"\63\212IE\20'\302x@\220\7\310x\240\204\7Lx\300\210\7\310x\200\220\7\204:\10\0\361\263"
",\224&\332g@\362*g\271\323\20KK\251\7BZ\244\10\64\206\30\243\10R\206\30C$\21\4"
"\11+\34\241\2\42\203\260\262\0\361\264\60\267\42\336J\320\61\211\254\342\201\7\242\227\334b\17\210\63F"
"B\341\34e\210@\243\24C\230 \304\205!b A\204&H\21b\30\264\20\0\361\265:\365\342\331"
"'\320\360\2\11#,a\204+O\220\261\304\60\247\10\244\134y@\220\7\206x\200\210\7Jx\200\214"
"\7\306x@\30\307\310\7%\230\320B\21l\220\261\6\31\5\0\361\266:\324\42\332'\314\340\302\21J"
"\10!B+N\14c\310@+\241G\36\20\342\1\22\36 \341\1\22\36 \341\201\61\36\10\312\260\60"
"\205\10C\244!\302\20i\204!D\33\10\0\361\267/\264&\332\210\70\264\324y \214\7Fx\340\37"
"P\341\201\61\36\10\311|P\6\13D\220p\206(B\230\21\212\30F\224@\4\13\63(\0\361\270\60"
"\224&\332\210\70\264\324y \214\7Fx\340\37P\341\201\61\36\10\311lq\206!\42\20A\202\20c"
"\210\42\204\31\241\210aD\11D\260\240\0\361\271\65\265&\332\250\70\304\24z \220\7Hx\240\204\7"
"Jx\340\201\25\36(\342\201A\36\10\312|pF\13D\224p\206(b\230\21\312\20G\224\60\3\15"
"\13\0\361\272-u\342\332\250<\304\24z \220\7\206x\240\204\7\36X\341\201\22\36(\342\201A\36"
"\10\312|\220\203\11L\24\301\6\31K\24a\0\361\273+\224\246\326\247\270\303\22z\344\1!\36 \341"
"\201\77@\304\3c<U>\10!\5$\222\70c\204\61\216 \201\10\66\342H\0\361\274\61\225\342\332"
"\250\270\304\22z@\214\7\210x\340\17\240\360\0\31\17\210t\340\370\0\210\21N\30B\10\61\312\20\302"
"\14\21\304Pb\10!\334\230\42\1\361\275=\225\242\332\210\274\323Rz\345\1A\36\30\242\21!\226("
"\4\211$PX\303\204uFHo\354\60\302\11#\220 \304\21\42\220 \206\10b\210@\202\20C\10"
"!\2\11f\230\260F\2\361\276*U\42\333\250<\304\24z \214\7\210x\340\7Dx\240\204\7\310"
"x@\244\363\301\15$Ra\204\22R \321\13$\30\0\361\277<\226\346\336\347\70\225\332y\200\214\7"
"Jx\300\204\7~\240\210\7\12y ,\243B\15$\230@\302\30#\230\60\304\30b\220!\4\32!"
"\210\221D\21%\34AF\21&\224Q\2\1\361\300:\225\246\332\250<\264\326y \220\7Hx\240\204"
"\7\376\0\22\17\220\361\200P\346\204)DhA\210 F\60!\10!\302\20\301\204\60B\30Q\20#"
"\210\240B\30\42<\241\0\361\301:\225\246\332\250<\264\326y \220\7Hx\240\204\7\376@\12\17\220"
"\361\200P\346\204)DhA\10\21F\60A\4!D\30A\204\21D\64\42\21F\20A\5\21F\200"
"A\1\361\302\62\225f\332\250\270\304\24z \214\7\210x\340\17\240\360\0\21\17\20\362J(\245\4\22"
"\236\20b\204\23\206\30\321\21\42\234Q\306\12#zc\6\5\0\361\303\60\265\246\336\250\270\264\326y@"
"\214\7Hx\240\204\7\376@\12\17\220\361\200H'\206\17\224\30!\205\22HHb\210\61\220\30b\214"
"\26hX\0\361\304\60\226\242\332\250\274\324\24z@\220\7\212x\240\204\7~\300\204\7\312x\200\220\7"
"\302\62\37\240p\4\13$\10a\304\220\224\30b\14\27jh\0\361\305\66\266\242\336\251<\324Tz "
"\220\7\212x\340\17\260\360@\21\17\24\362@Xf\207\32\210\20a\210\21\304\30\42\14\21\206\20\303\14"
"\21\306XA\210!\236\240\201\1\361\306\65\225b\332\250<\304Rz \214\7\210x\340\17\240\360\0\31"
"\17\210t\242\330\201\10!D\30A\214!\204\20a\4\61N\20\62\24\204\20\201\11\21F\200a\1\361"
"\307=\265\242\332\250<\304Rz\344\1\42\36\370\3(<P\302\3\204<\20\322\311\201\6\42\204\20a"
"\4\61\206\20B\4!\304\70AHK\10!\2\11D\4!\302\10$\20Q\2\12\2\0\361\310\66\324"
"&\332M\304\1\11\14A\300 \304*C$\64\306pA\204\7\206x`\204\7Hx\340\201$\36\30"
"\342\201\61\236*\37\214`\302\22E\254A\206\22E\30\0\361\311:\324&\332-H\21G\14A\314\300"
"\212\20\352\210R\22\21\342\205 \36\20\342\1\22\36 \341\1\22\36 \342\1A^:,\234\240\304\10"
"C\244!\202\30I\210\21F\33\11\0\361\312+u\342\326&L\61G$\261\274\323PKK\255U\2"
"Yd\10F\206h\242\204&J`\342\204%NP\343\10T\252\63\4\0\361\313'u\346\336\250\270\264"
"\326y@\214\7\210x\340\17\240\360@\11\17\20\362V\371 \7\23\330\30d\215AX\60\341\0\361\314"
",\225\242\332\250<\304\24z \214\7\210x\340\7Dx\240\204\7\310x@\244\363\201\10)$b\6"
"\32g\244@\42\67\344\230A\1\361\315<\267b\336\251D\344\224z \224\7\10y\240\210\7\214x\300"
"\210\7\214x\300\214\7\210y \224`\214\11\201\300\21\6\34\42\214\220\2\11f\234\241\306\10c\254\60"
"\306\10p\324\260\0\361\316:\327\42\336\251D\344\224z \224\7\10y\240\210\7\214x\300\210\7\214x"
"\300\214\7\210y&\230rB \360\34q\206\10#\220\230\31C\210\241\306\31+\220\10\16:jX\0"
"\361\317+u\242\332\250<\304\24z \220\7\206x\240\204\7~@\204\7Jx\200\220\7\202\62\37\344"
"P\202\13\204\254\61\6\23%\34\0\361\320\63\225\246\332\250\270\264\226y`\214\7Hx\240\204\7~@"
"\210\7\310x@\244\363\301\11/\220\60\302\12$\204\202\206\220\324\20A\24$F \341\205\5\0\361\321"
"\67\324\42\332'\314\340\2\11#(aD+N\20\261\304\60\246\10\224\34y@\210\7Hx\200\204\7"
"Hx\200\210\7\206x@$\3E\15-\220\261\306\30k\220`\0\361\322:\364\342\331&\314\360\302\10"
"$(aD+N\214\261\204\70\206\210\224\34y@\210\7Hx\200\204\7Hx\200\204\7\210x@\24"
"\267\310\7B\224\260\310\30\212\214\301B\11\6\0\361\323\60\265\242\332\250<\304\24z \214\7\210x\340"
"\17\240\360@\11\17\20\362@P\346\203\33\210\30!\215\21\306@c\204\61R\30\202\4'fX\0\361"
"\324\65\265\246\336\250\70\304\24z \220\7Hx\240\204\7\376@\12\17\220\361\200\60! \21\30\321\203"
"\4\42F\30\323\10c\214@\306\10c\244\60\4\11N\314\320\0\361\325:\225f\336\211<\304\24z "
"\220\7\206x\240\204\7\36X\341\201\22\36(\341\1B\36\10\312\344\60\206\11#\210\311\4\61\2\21A"
"\14\21\304\20\341\214 F`A\4\32\26\0\361\326:\325&\332M\310\21I\14A\304 D#C("
"$JQC\214\7B\10\342\201\61\36 \341\201\22\36(\341\201\42\36\30\344\201\240\314\7$\224\340\302"
"(k\210\262\6\11\10\0\361\327\70\324*\336-H\21G\14A\300 \2+#\244$\210x\344\1!"
"\36 \341\1\22\36 \341\1\22\36 \342\1Q\202\70\42,\362\301\10&\254A\206\32\203\254`\302\1"
"\361\330<r\256\336(\70\21B\20\254\244@\306\20E\220@\304 $\20\62F(\201\210\21\222\20\346"
"\254\313\10\221B\12%\220\61\15\22D\10$\20\62\304\30$\224@H\13!\4\341B\2\361\331\36\264"
"%\333\210\70\264\324y \214\7Fx\340\3\203=\360\200\12\17\214\361@H\346\0\361\332!U\342\332"
"\347\264\264\326y@\214\7Hx\340\17\244\360\0\31\17\210tb\370\240\270\17\234\63\0\361\333\33V\241"
"\333D\254`\254\201\10\12J<\360\7BHB\205c\16!\250\10\0\361\334.\64\352\336\42\250aD"
"\21d\310\201\2\62%\14\61\310+D\240R\302\61+\11\66\6$&\20!H\63G\11\221\204\31)"
"\220\242\220\1\361\335-\364\345\332(\214\360B\20a\70\61\306\21C\220QF\42E\4a\310\10%\224"
"\62\202*\315\254C\330\25N\210\320B\30N\210p\0\361\336&q\256\336\345 u\204\31E Q\304"
"\23oJ!<\360\0AE\4F\236\200\342ID Q\10!\7\21\0\361\337.\221\252\326\344 u"
"\204\31E Q\304\23+\210\7Bx\340\201Q\210\11\250<\22\202x \204\7\202\23O\20\201D!"
"\204\30\245\310\1\361\340\42\225\246\336\347\264\224\134y`\214\7Hx\340\17\244\360\0\31\17\210t\242\370"
"\200\70\354>\240\216\0\361\341&\265f\336\250\270\264\326y@\214\7\210x\340\3\244\241\366\200\11\17\220"
"\361\200H\7\212\17\212\303\356\3\352\10\0\361\342,V\242\332C\60a\306\32&\264\320\305\12\306\230#"
"\322@a\205\7~\240\4\25\322H\1\225CH\42<\264`\306\32E\60\61\0\361\343\64V\242\332C"
"\60a\6\21e\230PF\11.\70\261\202\61\346\210\64PX\341\201\37(A\205\64R@\345\20\222H"
"\13.\224Q\202\31D\224Q\4\23\3\0\361\344;V\242\332B\70\61\306\20D\214\61\2\31b\220\240"
"\2\11L$R\214\71\2\215\7Jx\340\7JX\1\215$\216\61\205\244\261\2\11*\220!\6\11c"
"\14A\304\30C\70!\0\361\345$\225\342\332\7\61\205DP\347\1\61\36 B\251\7\36`\341\270\363"
"\36\30\10\275\361Aq\330\35\347\335\0\361\346'\225\242\332\7\61\205\232y`\214\7HX\352\201\7X"
"\70\356\274\7D:\61|PHX\227\5\362\1$a\11\0\361\347\67\316\66\332\203$C\302\60$\14"
"#\210\60$\14!\4\22B !\4\22B !\4\22B !\4\22B\234!F!\202\220\61\10"
"\31\203\20\22\12\321\12B\344\0\361\350%\316\66\332c\250\222\14\62\202\10\203\354\207\204\20g\210Q\210"
" d\14B\306 \204\204B\24I\6!r\0\361\351=\323%\327\341\1\21\36x@\205\21H\20\242"
"\10\21H\20\242\10\21\206\20\242\204(\214 B\20$\204\61\202\24H\20B\42$\10!\202\20B\214"
"\60\202$Fx\340\201\24\36\20\1\0\0";
#endif /* U8G2_USE_LARGE_FONTS */

158
src/weather.cpp Normal file
View File

@@ -0,0 +1,158 @@
#include "weather.h"
#include <_preference.h>
TaskHandle_t WEATHER_HANDLER;
String _qweather_host;
String _qweather_key;
String _qweather_loc;
int8_t _weather_status = -1;
int8_t _weather_type = -1;
Weather _weather_now = {};
DailyWeather dailyWeather = {};
DailyForecast _daily_forecast = {
.weather = &dailyWeather,
.length = 1
};
Weather hourlyWeather = {};
HourlyForecast _hourly_forecast = {
.weather = &hourlyWeather,
.length = 1,
.interval = 2
};
int8_t _hourly_weather_status = -1;
Hitokoto _hitokoto = {};
int8_t _hitokoto_status = -1;
int8_t weather_type() {
return _weather_type;
}
int8_t weather_status() {
return _weather_status;
}
Weather* weather_data_now() {
return &_weather_now;
}
DailyForecast* weather_data_daily() {
return &_daily_forecast;
}
HourlyForecast* weather_data_hourly() {
return &_hourly_forecast;
}
int8_t hourly_weather_status(){
return _hourly_weather_status;
}
int8_t hitokoto_status() {
return _hitokoto_status;
}
Hitokoto* hitokoto_data() {
return &_hitokoto;
}
void task_weather(void* param) {
Serial.println(F("[Task] get weather begin..."));
Preferences pref;
pref.begin(PREF_NAMESPACE);
_weather_type = pref.getString(PREF_QWEATHER_TYPE).compareTo("1") == 0 ? 1 : 0;
pref.end();
Serial.printf("Weather Type: %d\n", _weather_type);
API<> api;
bool success;
// 一言
Serial.println(F("get hitokoto begin..."));
success = api.getHitokoto(_hitokoto);
if (success) {
_hitokoto_status = 1;
} else {
_hitokoto_status = 2;
}
Serial.println(F("get hitokoto end..."));
// 逐小时预报
success = api.getForecastHourly(_hourly_forecast, _qweather_host.c_str(), _qweather_key.c_str(), _qweather_loc.c_str());
if (success) {
_hourly_weather_status = 1;
} else {
_hourly_weather_status = 2;
}
// 实时天气
if (_weather_type == 0) {
success = api.getForecastDaily(_daily_forecast, _qweather_host.c_str(), _qweather_key.c_str(), _qweather_loc.c_str());
} else {
success = api.getWeatherNow(_weather_now, _qweather_host.c_str(), _qweather_key.c_str(), _qweather_loc.c_str());
}
if (success) {
_weather_status = 1;
} else {
_weather_status = 2;
}
Serial.println(F("[Task] get weather end..."));
WEATHER_HANDLER = NULL;
vTaskDelete(NULL);
}
void weather_exec(int status) {
_weather_status = status;
if (status > 0) {
return;
}
if (!WiFi.isConnected()) {
_weather_status = 2;
return;
}
// Preference 获取配置信息。
Preferences pref;
pref.begin(PREF_NAMESPACE);
_qweather_host = pref.getString(PREF_QWEATHER_HOST, "api.qweather.com");
_qweather_key = pref.getString(PREF_QWEATHER_KEY, "");
_qweather_loc = pref.getString(PREF_QWEATHER_LOC, "");
pref.end();
if (_qweather_key.length() == 0 || _qweather_loc.length() == 0) {
Serial.println(F("Qweather key/locationID invalid."));
_weather_status = 3;
return;
}
if (WEATHER_HANDLER != NULL) {
vTaskDelete(WEATHER_HANDLER);
WEATHER_HANDLER = NULL;
}
xTaskCreate(task_weather, "WeatherData", 1024 * 10, NULL, 2, &WEATHER_HANDLER);
}
void weather_stop() {
if (WEATHER_HANDLER != NULL) {
vTaskDelete(WEATHER_HANDLER);
WEATHER_HANDLER = NULL;
}
_weather_status = 2;
}

125
src/web_server.txt Normal file
View File

@@ -0,0 +1,125 @@
#include <WiFi.h>
#include <WebServer.h>
// WiFi配置
const char* ap_ssid = "ESP32_Config";
const char* ap_password = "12345678";
// Web Server实例
WebServer server(80);
// 存储配置
String wifi_ssid = "";
String wifi_password = "";
int timezone = 8;
void setup() {
Serial.begin(115200);
// 启动AP模式
WiFi.softAP(ap_ssid, ap_password);
Serial.println("AP模式已启动");
Serial.print("IP地址: ");
Serial.println(WiFi.softAPIP());
// 配置路由
server.on("/", handleRoot);
server.on("/save", handleSave);
// 启动Web Server
server.begin();
Serial.println("HTTP服务器已启动");
}
void loop() {
server.handleClient();
}
// 处理根路径请求
void handleRoot() {
// 读取HTML文件内容
String html = R"=====(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>设备配置</title>
<style>
/* 样式内容 */
</style>
</head>
<body>
<div class="container">
<h1>设备配置</h1>
<form id="config-form" action="/save" method="POST">
<div class="form-group">
<label for="wifi-ssid">WiFi SSID</label>
<input type="text" id="wifi-ssid" name="wifi-ssid" required>
</div>
<div class="form-group">
<label for="wifi-password">WiFi 密码</label>
<input type="password" id="wifi-password" name="wifi-password">
</div>
<div class="form-group">
<label for="timezone">时区</label>
<select id="timezone" name="timezone">
<option value="8">GMT+8 中国标准时间</option>
<option value="0">GMT 格林威治时间</option>
<option value="-5">GMT-5 美国东部时间</option>
</select>
</div>
<button type="submit">保存配置</button>
</form>
</div>
<script>
// JavaScript内容
</script>
</body>
</html>
)=====";
server.send(200, "text/html", html);
}
// 处理保存配置请求
void handleSave() {
if (server.method() == HTTP_POST) {
// 获取表单数据
wifi_ssid = server.arg("wifi-ssid");
wifi_password = server.arg("wifi-password");
timezone = server.arg("timezone").toInt();
// 保存配置到EEPROM可选
// saveConfigToEEPROM();
// 返回成功响应
server.send(200, "text/plain", "配置保存成功");
// 尝试连接新配置的WiFi
connectToWiFi();
}
}
// 连接WiFi
void connectToWiFi() {
if (wifi_ssid.length() > 0) {
Serial.println("正在连接WiFi...");
WiFi.begin(wifi_ssid.c_str(), wifi_password.c_str());
int retry = 0;
while (WiFi.status() != WL_CONNECTED && retry < 10) {
delay(1000);
Serial.print(".");
retry++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi连接成功");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\nWiFi连接失败");
}
}
}

11
test/README Normal file
View File

@@ -0,0 +1,11 @@
This directory is intended for PlatformIO Test Runner and project tests.
Unit Testing is a software testing method by which individual units of
source code, sets of one or more MCU program modules together with associated
control data, usage procedures, and operating procedures, are tested to
determine whether they are fit for use. Unit testing finds problems early
in the development cycle.
More information about PlatformIO Unit Testing:
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html