Files
readguy/src/readguy.h
2023-11-03 18:48:56 +08:00

375 lines
19 KiB
C++

/******************** F r i e n d s h i p E n d e r ********************
* 本程序隶属于 Readguy 开源项目, 请尊重开源开发者, 也就是我FriendshipEnder.
* 如果有条件请到 extra/artset/reward 中扫描打赏,否则请在 Bilibili 上支持我.
* 项目交流QQ群: 926824162 (萌新可以进来问问题的哟)
* 郑重声明: 未经授权还请不要商用本开源项目编译出的程序.
* @file readguy.h
* @author FriendshipEnder (f_ender@163.com), Bilibili: FriendshipEnder
* @brief readguy 基础功能 头文件.
* @version 1.0
* @date 2023-09-21
* @attention
* Copyright (c) 2022-2023 FriendshipEnder
*
* Apache License, Version 2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef _GUY_DRIVER_H_FILE
#define _GUY_DRIVER_H_FILE
// ------------------------- includings - 包含 ----<<<<
#include <Arduino.h>
#include <SPI.h>
#include <FS.h>
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
#include "guy_epaper/guy_epdbase.h"
#if (defined(READGUY_DEV_154A) || defined(READGUY_DEV_290A))
#include "guy_epaper/guy_154a_290a/guy_154a_290a.h"
#endif
#if (defined(READGUY_DEV_154B) || defined(READGUY_DEV_270B) || defined(READGUY_DEV_290B))
#include "guy_epaper/guy_154b_270b_290b/guy_154b_270b_290b.h"
#endif
#ifdef READGUY_DEV_213A
#include "guy_epaper/guy_213a/guy_213a.h"
#endif
#if (defined(READGUY_DEV_213B) || defined(READGUY_DEV_266A))
#include "guy_epaper/guy_213b_266a/guy_213b_266a.h"
#endif
#ifdef READGUY_DEV_370A
#include "guy_epaper/guy_370a/guy_370a.h"
#endif
#ifdef READGUY_DEV_420A
#include "guy_epaper/guy_420a/guy_420a.h"
#endif
#ifdef READGUY_DEV_420B
#include "guy_epaper/guy_420b/guy_420b.h"
#endif
#ifdef MEPD_DEBUG_DISPLAY
#include "guy_epaper/lcdDebug/lcdDebug.h"
#endif
#include "guy_button.h" //改自Button2精简而来
#include "guy_version.h"
#include "guy_driver_config.h" //config
#ifdef READGUY_USE_LITTLEFS
#include <LittleFS.h>
#else
#ifndef ESP8266
#include <SPIFFS.h>
#endif
#endif
#if defined(ESP8266) //for ESP8266
#ifdef DYNAMIC_PIN_SETTINGS
#include <EEPROM.h> //ESP32需要NVS才可以读取引脚信息
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>
#include "ESP8266HTTPUpdateServer.h"
#endif
#include <SDFS.h>
#include <Ticker.h>
#else //for ESP32
#ifdef DYNAMIC_PIN_SETTINGS
#include <Preferences.h> //ESP32需要NVS才可以读取引脚信息
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include "HTTPUpdateServer.h"
#endif
#include <SD.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#endif
#ifdef DYNAMIC_PIN_SETTINGS
#define READGUY_cali (config_data[0])
#define READGUY_shareSpi (config_data[1])
#define READGUY_epd_type (config_data[2]) // 对应的epd驱动程序代号, -1为未指定
//显示驱动部分, 显示默认使用vspi (vspi也是默认SPI库的通道)
#define READGUY_epd_mosi (config_data[3]) // 目标显示器的 MOSI 引脚
#define READGUY_epd_sclk (config_data[4]) // 目标显示器的 SCLK 引脚
#define READGUY_epd_cs (config_data[5]) // 目标显示器的 CS 引脚
#define READGUY_epd_dc (config_data[6]) // 目标显示器的 DC 引脚
#define READGUY_epd_rst (config_data[7]) // 目标显示器的 RST 引脚
#define READGUY_epd_busy (config_data[8]) // 目标显示器的 BUSY 引脚
//sd卡驱动部分, 默认使用hspi (sd卡建议用hspi)
#define READGUY_sd_miso (config_data[9]) // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_mosi (config_data[10])// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_sclk (config_data[11])// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
#define READGUY_sd_cs (config_data[12])// 目标sd卡的 CS 引脚.
#define READGUY_i2c_sda (config_data[13])// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
#define READGUY_i2c_scl (config_data[14])// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
//注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发
#define READGUY_btn1 (config_data[15])
#define READGUY_btn2 (config_data[16])
#define READGUY_btn3 (config_data[17])
#define READGUY_bl_pin (config_data[18])//前置光接口引脚IO
#define READGUY_rtc_type (config_data[19])//使用的RTC型号(待定, 还没用上)
#define READGUY_sd_ok (config_data[20]) //SD卡已经成功初始化
#define READGUY_buttons (config_data[21]) //按钮个数, 0-3都有可能
#endif
class ReadguyDriver: public LGFX_Sprite{ // readguy 基础类
public:
#ifdef READGUY_ESP_ENABLE_WIFI
#ifdef ESP8266
typedef ESP8266WebServer ReadguyWebServer;
typedef ESP8266HTTPUpdateServer ReadguyUpdateServer;
#else
typedef WebServer ReadguyWebServer;
typedef HTTPUpdateServer ReadguyUpdateServer;
#endif
#endif
ReadguyDriver();
/** @brief 初始化readguy
* @param WiFiSet 是否保持AP模式关闭. 0:配网完成自动关WiFi, 1:需要手动调用 WiFi.mode(WIFI_OFF) 关闭WiFi.
* 2:自动连接到已存的WiFi, 但不等待连接成功
* @return SD卡是否就绪
*/
uint8_t init(uint8_t WiFiSet = 0);
/// @brief 设置显示亮度
void setBright(int d);
/// @brief 返回显示亮度
int getBright() const { return currentBright; }
/// @brief 刷新显示到屏幕上
void display(bool part = true);
/** @brief 刷新显示到屏幕上, 可以自定义读取指定位置像素的函数
* @param f 自定义的函数. 此函数将在读取像素并输出到墨水屏时被调用.
* 每次调用需要返回 "参数对应位置" 的8个像素的颜色信息(凑成一字节). 其中左侧应在高位,右侧应在低位.
* 例如, 调用f(0)将会返回屏幕最左上角的一横排的8个像素的颜色值.
*
* * 可以用下述方法来将此 "参数对应位置" 还原为 X和Y坐标. 返回的X,Y坐标位置所在像素点以及其后面的
* 7个像素点共同组成了需要返回位置的8个像素点. 将这8个像素点拼合为一字节之后返回该函数即可.
* @code C++
* int w = ( ( drv_width() + 7 ) >> 3 ); //其中 k 为此自定义函数的参数, w为临时变量
* int x = ( ( k % w ) << 3 ); // x 为具体像素的横坐标
* int y = k / w; // y 为具体像素的纵坐标
* @endcode
* 该函数会将参数从0开始,每次逐渐增加1的顺序来被调用. 即先调用f(0),再f(1),f(2),f(3)... 以此类推.
*/
void display(std::function<uint8_t(int)> f, bool part = true);
/// @brief 显示图片, 使用抖动算法. 可以用省内存的方法显示
void drawImage(LGFX_Sprite &spr,uint16_t x,uint16_t y);
/// @brief 设置显示对比度(灰度)
void setDepth(uint8_t d);
/** @brief 返回目标屏幕是否支持16级灰度 返回非0代表支持.
* @note 返回负整数则代表调用draw16greyStep需要从深色到浅色刷新, 而不是从浅色到深色刷新 */
int supportGreyscaling() const{return READGUY_cali==127?guy_dev->drv_supportGreyscaling():0;}
/** @brief 设置灰度的渲染画质. 高画质模式在某些屏幕某些情况下可能表现不好.
* @param q 0-关闭连续刷屏 开启16阶灰度抖动 1-开启连续刷屏 开启16阶灰度抖动
* 2-关闭连续刷屏 关闭16阶灰度抖动 3-开启连续刷屏 关闭16阶灰度抖动 */
void setGreyQuality(uint8_t q) { if(READGUY_cali==127) guy_dev->setGreyQuality(q); }
/// @brief 显示灰度图片,如果支持,否则就不显示灰度图片了. 可以用省内存的方法显示
void draw16grey(LGFX_Sprite &spr,uint16_t x,uint16_t y);
/** @brief 按照自定义分步显示灰度图片,如果支持,否则就不显示灰度图片了. 可以用省内存的方法显示
* @param step 步骤代号. 从1开始到15,依次调用此函数来自定义的灰度显示显存内容. 没有0和16.
* @note 必须按照 "慢刷全屏->绘图->设置参数1->绘图->设置参数2... 调用15次 来完成一次自定义灰度刷屏
* 连续调用多次此函数之间, 可以修改显存内的像素颜色, 但只能从白色改为黑色.
* @attention 需要先调用 supportGreyscaling() 来确定是否支持灰度分步刷新.为负数时需要从深到浅刷新
*/
void draw16greyStep(int step);
/** @brief 分步刷新显示灰度, 详见 display(f,part) 和 draw16grey(spr,x,y) 的注释.
* @note 此函数不读取显存,而是通过调用该函数来确定像素颜色. */
void draw16greyStep(std::function<uint8_t(int)> f, int step);
/// @brief 对缓冲区内所有像素进行反色.只对现在的缓冲区有效,之后的颜色该怎样就怎样. 灰度信息会被扔掉.
void invertDisplay();
/// @brief 进入EPD的低功耗模式
void sleepEPD(void);
/// @brief ap配网设置页面
typedef struct {
String linkname;
String event; //链接名称 事件URI
std::function<void(ReadguyWebServer*)> func; //触发时执行的函数
} serveFunc;
#ifdef READGUY_ESP_ENABLE_WIFI
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void ap_setup();
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void server_setup(const String &notify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0);
bool server_loop();
void server_end();
#else
/// @brief 初始化WiFi AP模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void ap_setup(){}
/// @brief 初始化服务器模式, 用于将来的连接WiFi 处于已连接状态下会断开原本的连接
void server_setup(const String &notify=emptyString, const serveFunc *serveFuncs = nullptr, int funcs = 0){}
bool server_loop(){ return true; }
void server_end(){}
#endif
/// @brief 检查初始化屏幕硬件, 若检查失败返回0,否则返回硬件代号
uint8_t checkEpdDriver();
/** @brief 初始化屏幕, 设置驱动代号, 引脚排列顺序. 过程会检验引脚可用性.
* @param g_width, g_height 显示区域的宽度和高度. 为0表示直接使用屏幕的宽度和高度
* @note 这两个参数转专为指定分辨率的程序画面设计, 其他分辨率的画面会自动拉伸. [1.2新增] */
void setEpdDriver(int g_width = 0,int g_height = 0);
/** @brief 初始化SD卡, 设置驱动代号, 引脚排列顺序. 过程会检验引脚可用性.
* @return SD卡初始化成功与否 */
bool setSDcardDriver();
/// @brief 初始化按钮, 背光, 设置驱动代号, 引脚排列顺序
void setButtonDriver();
/** @brief 检查SD卡是否插入
* @param check 为true时, 如果SD卡不可用则初始化SD卡. 为false时, 当SD卡不可用则返回LittleFS. */
bool SDinside(bool check=true) { return check?setSDcardDriver():READGUY_sd_ok; };
/// @brief 检查按钮. 当配置未完成时,按钮不可用, 返回0.
uint8_t getBtn() { return (READGUY_cali==127)?getBtn_impl():0; }
/** @brief 返回可用的文件系统. 当SD卡可用时, 返回SD卡. 否则根据情况返回最近的可用文件系统
* @param initSD 2:总是重新初始化SD卡; 1:若SD卡不可用则初始化; 0:SD卡不可用则返回LittleFS. */
fs::FS &guyFS(uint8_t initSD = 0);
//friend class EpdIf; //这样EpdIf就可以随意操作这个类的成员了
private:
//以下是支持的所有屏幕型号 Add devices here!
//添加屏幕驱动范例: 直接添加对应屏幕的类就可以用了
static const char projname[8];
static const char tagname[7];
//uint8_t config_wifi=0; //是否强行在初始化期间设置WiFi.
#ifdef DYNAMIC_PIN_SETTINGS//数据是否已经校准
int8_t config_data[22];
char randomch[4]; //校验用字符串
void nvs_init(); //初始化持久存储器.
void nvs_deinit();//保存持久存储器的内容
bool nvs_read(); //从持久存储器读取, 返回是否读取成功
void nvs_write(); //写入到持久存储器
#else
int8_t READGUY_sd_ok = 0;
int8_t READGUY_cali = 0;
int8_t READGUY_buttons = 0; //按钮个数, 0-3都有可能
#endif
int epd_OK=0; //墨水屏可用
int currentBright = -3; //初始亮度
int16_t guy_width=0,guy_height=0;
//LGFX_Sprite gfx; // 图形引擎类指针, 可以用这个指针去操作屏幕缓冲区
readguyEpdBase *guy_dev = nullptr;
//内部调用函数: 初始化epd
//template <class T> T t_init(T t, bool initial = true);
//template <class T> void t_display(T t);
#if defined(ESP8266)
//对于esp8266, 需要注册到ticker
Ticker btnTask;
#else
#ifdef DYNAMIC_PIN_SETTINGS
//NVS数据操作函数, 无NVS的使用EEProm的最后几个字节块
Preferences nvsData;
#endif
static SPIClass *sd_spi;
static SPIClass *epd_spi;
static TaskHandle_t btn_handle;
#endif
#ifdef READGUY_ESP_ENABLE_WIFI
ReadguyWebServer sv;
ReadguyUpdateServer httpUpdater;
String guy_notify=emptyString; //嵌入在网页中的自定义标语
int sfuncs=-1;
String* sfnames=nullptr;
String* sfevents=nullptr;
void handleInit(); //服务器初始化系统(初次访问时, 跳转至引脚设定函数)
void handleInitPost(); //服务器响应初始化请求
void handlePinSetup(); //服务器-引脚设定函数
void handleFinal(); //服务器-校验屏幕是否正常
void handleFinalPost(); //服务器-校验屏幕是否正常回调函数
//void handleWiFi();//[已弃用]服务器-处理WiFi连接相关内容和API接口密钥功能
void handleNotFound(); //服务器-404响应
#endif
//按键驱动部分
static guy_button btn_rd[3];
/// @brief 复用输出引脚1: 适用于按键引脚与屏幕DC引脚复用的情形
/// @note 只能解决屏幕DC引脚复用的情况, 其他引脚最好不要复用, 复用了我也解决不了
static int8_t pin_cmx;
static volatile uint8_t spibz;
private:
#ifdef READGUY_ESP_ENABLE_WIFI
//static constexpr size_t EPD_DRIVERS_NUM_MAX = READGUY_SUPPORT_DEVICES;
static const char *epd_drivers_list[EPD_DRIVERS_NUM_MAX];
static const PROGMEM char html_header[]; //HTML头的数据. 省内存, 能省一点是一点
static const PROGMEM char index_cn_html[];
static const PROGMEM char index_cn_html2[];
static const PROGMEM char index_cn_html3[];
static const PROGMEM char index_cn_html16[];
static const PROGMEM char verify_html[];
static const PROGMEM char verify2_html[];
static const PROGMEM char verifybtn_html[3][200];
static const PROGMEM char final_html[];
static const PROGMEM char afterConfig_html[];
static const PROGMEM char home_html[];
static const PROGMEM char end_html[];
//static const PROGMEM uint8_t faviconData[1150];
#endif
static void looptask(); //按键服务函数
static uint8_t rd_btn_f(uint8_t btn);
uint8_t getBtn_impl(); //按钮不可用, 返回0.
static void in_press(){ //SPI开始传输屏幕数据
#ifndef ESP8266
if(!spibz) epd_spi->beginTransaction(SPISettings(ESP32_DISP_FREQUENCY, MSBFIRST, SPI_MODE0));
#endif
spibz ++;
}
static void in_release(){//SPI结束传输屏幕数据
spibz --;
#ifndef ESP8266
if(!spibz) epd_spi->endTransaction();
#endif
}
public: //增加了一些返回系统状态变量的函数, 它们是静态的, 而且不会对程序造成任何影响.
constexpr int getShareSpi() const { return config_data[1]; }
constexpr int getEpdType () const { return config_data[2]; } // 对应的epd驱动程序代号, -1为未指定
//显示驱动部分, 显示默认使用vspi (vspi也是默认SPI库的通道)
constexpr int getEpdMosi () const { return config_data[3]; } // 目标显示器的 MOSI 引脚
constexpr int getEpdSclk () const { return config_data[4]; } // 目标显示器的 SCLK 引脚
constexpr int getEpdCs () const { return config_data[5]; } // 目标显示器的 CS 引脚
constexpr int getEpdDc () const { return config_data[6]; } // 目标显示器的 DC 引脚
constexpr int getEpdRst () const { return config_data[7]; } // 目标显示器的 RST 引脚
constexpr int getEpdBusy () const { return config_data[8]; } // 目标显示器的 BUSY 引脚
//sd卡驱动部分, 默认使用hspi (sd卡建议用hspi)
constexpr int getSdMiso () const { return config_data[9]; } // 目标sd卡的 MISO 引脚, sd_share_spi == 1 时无效
constexpr int getSdMosi () const { return config_data[10]; }// 目标sd卡的 MOSI 引脚, sd_share_spi == 1 时无效
constexpr int getSdSclk () const { return config_data[11]; }// 目标sd卡的 SCLK 引脚, sd_share_spi == 1 时无效
constexpr int getSdCs () const { return config_data[12]; }// 目标sd卡的 CS 引脚.
constexpr int getI2cSda () const { return config_data[13]; }// 目标i2c总线的SDA引脚, 当且仅当启用i2c总线时才生效
constexpr int getI2cScl () const { return config_data[14]; }// 目标i2c总线的SCL引脚, 当且仅当启用i2c总线时才生效
//按键驱动部分, 为负代表高触发, 否则低触发,
//注意, 这里的io编号是加1的, 即 1或-1 代表 gpio0 的低触发/高触发
constexpr int getBtn1Pin () const { return config_data[15]; }
constexpr int getBtn2Pin () const { return config_data[16]; }
constexpr int getBtn3Pin () const { return config_data[17]; }
constexpr int getBlPin () const { return config_data[18]; } //前置光接口引脚IO
constexpr int getRtcType () const { return config_data[19]; } //使用的RTC型号(待定, 还没用上)
constexpr int getButtonsCount() const { return config_data[21]; } //按钮个数, 0-3都有可能
constexpr int memWidth () const { return guy_width ; } //返回显存宽度(不是画幅宽度),不会随着画布旋转改变
constexpr int memHeight () const { return guy_height ; } //返回显存高度(不是画幅高度),不会随着画布旋转改变
int drvWidth () const { return READGUY_cali==127?guy_dev->drv_width():0; } //返回显示屏硬件宽度(不是画幅宽度)
int drvHeight() const { return READGUY_cali==127?guy_dev->drv_height():0; } //返回显示屏硬件高度(不是画幅高度)
// private:
void implBeginTransfer() { guy_dev->BeginTransfer(); } //此函数用于开启SPI传输, 只能在自定义刷屏函数中使用!!
void implEndTransfer() { guy_dev->EndTransfer(); } //此函数用于开启SPI传输, 只能在自定义刷屏函数中使用!!
/// @brief 分阶段显示图片, 使用抖动算法. 更加的省内存.目前函数
void drawImageStage(LGFX_Sprite &spr,uint16_t x,uint16_t y,uint8_t stage,uint8_t totalstage);
};
#endif /* END OF FILE. ReadGuy project.
Copyright (C) 2023 FriendshipEnder. */