├── data ├── CNAME ├── index.html.gz ├── js │ └── index.js.gz └── css │ └── index.css.gz ├── image ├── 152.png ├── 153.png └── web.png ├── .gitignore ├── partitions_custom.csv ├── .vscode ├── extensions.json └── settings.json ├── test └── README ├── platformio.ini ├── lib └── README ├── include └── README ├── README.md └── src ├── mycrypto ├── mycrypto.h └── mycrypto.cpp ├── i2s_adc.hpp ├── wave_gen.hpp ├── main.cpp └── mywebsocket ├── mywebsocket.h └── mywebsocket.cpp /data/CNAME: -------------------------------------------------------------------------------- 1 | unplugin.element-plus.org 2 | -------------------------------------------------------------------------------- /image/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guohaomeng/ESP32WebScope/HEAD/image/152.png -------------------------------------------------------------------------------- /image/153.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guohaomeng/ESP32WebScope/HEAD/image/153.png -------------------------------------------------------------------------------- /image/web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guohaomeng/ESP32WebScope/HEAD/image/web.png -------------------------------------------------------------------------------- /data/index.html.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guohaomeng/ESP32WebScope/HEAD/data/index.html.gz -------------------------------------------------------------------------------- /data/js/index.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guohaomeng/ESP32WebScope/HEAD/data/js/index.js.gz -------------------------------------------------------------------------------- /data/css/index.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guohaomeng/ESP32WebScope/HEAD/data/css/index.css.gz -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode/.browse.c_cpp.db* 3 | .vscode/c_cpp_properties.json 4 | .vscode/launch.json 5 | .vscode/ipch 6 | -------------------------------------------------------------------------------- /partitions_custom.csv: -------------------------------------------------------------------------------- 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, 0x1F0000, 5 | spiffs, data, spiffs, 0x200000,0x200000, 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "platformio.platformio-ide" 6 | ], 7 | "unwantedRecommendations": [ 8 | "ms-vscode.cpptools-extension-pack" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "esp_dsp.h": "c", 4 | "esp32-hal-timer.h": "c", 5 | "new": "cpp", 6 | "optional": "cpp", 7 | "random": "cpp", 8 | "limits": "cpp", 9 | "*.tcc": "cpp", 10 | "cmath": "cpp", 11 | "utility": "cpp" 12 | }, 13 | "commentTranslate.source": "Baidu" 14 | } -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for PlatformIO Unit Testing and project tests. 3 | 4 | Unit Testing is a software testing method by which individual units of 5 | source code, sets of one or more MCU program modules together with associated 6 | control data, usage procedures, and operating procedures, are tested to 7 | determine whether they are fit for use. Unit testing finds problems early 8 | in the development cycle. 9 | 10 | More information about PlatformIO Unit Testing: 11 | - https://docs.platformio.org/page/plus/unit-testing.html 12 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [env:esp32dev] 12 | platform = espressif32 13 | board = esp32dev 14 | framework = arduino 15 | board_build.f_cpu = 80000000L 16 | ;board_build.partitions = partitions_custom.csv -------------------------------------------------------------------------------- /lib/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link into executable file. 4 | 5 | The source code of each library should be placed in a an own separate directory 6 | ("lib/your_library_name/[here are source files]"). 7 | 8 | For example, see a structure of the following two libraries `Foo` and `Bar`: 9 | 10 | |--lib 11 | | | 12 | | |--Bar 13 | | | |--docs 14 | | | |--examples 15 | | | |--src 16 | | | |- Bar.c 17 | | | |- Bar.h 18 | | | |- library.json (optional, custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html 19 | | | 20 | | |--Foo 21 | | | |- Foo.c 22 | | | |- Foo.h 23 | | | 24 | | |- README --> THIS FILE 25 | | 26 | |- platformio.ini 27 | |--src 28 | |- main.c 29 | 30 | and a contents of `src/main.c`: 31 | ``` 32 | #include 33 | #include 34 | 35 | int main (void) 36 | { 37 | ... 38 | } 39 | 40 | ``` 41 | 42 | PlatformIO Library Dependency Finder will find automatically dependent 43 | libraries scanning project source files. 44 | 45 | More information about PlatformIO Library Dependency Finder 46 | - https://docs.platformio.org/page/librarymanager/ldf.html 47 | -------------------------------------------------------------------------------- /include/README: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for project header files. 3 | 4 | A header file is a file containing C declarations and macro definitions 5 | to be shared between several project source files. You request the use of a 6 | header file in your project source file (C, C++, etc) located in `src` folder 7 | by including it, with the C preprocessing directive `#include'. 8 | 9 | ```src/main.c 10 | 11 | #include "header.h" 12 | 13 | int main (void) 14 | { 15 | ... 16 | } 17 | ``` 18 | 19 | Including a header file produces the same results as copying the header file 20 | into each source file that needs it. Such copying would be time-consuming 21 | and error-prone. With a header file, the related declarations appear 22 | in only one place. If they need to be changed, they can be changed in one 23 | place, and programs that include the header file will automatically use the 24 | new version when next recompiled. The header file eliminates the labor of 25 | finding and changing all the copies as well as the risk that a failure to 26 | find one copy will result in inconsistencies within a program. 27 | 28 | In C, the usual convention is to give header files names that end with `.h'. 29 | It is most portable to use only letters, digits, dashes, and underscores in 30 | header file names, and at most one dot. 31 | 32 | Read more about using header files in official GCC documentation: 33 | 34 | * Include Syntax 35 | * Include Operation 36 | * Once-Only Headers 37 | * Computed Includes 38 | 39 | https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 10 | # ESP32WebScope 11 | 12 | 只用一块ESP32制作的ESP32网页示波器+波形发生器,可以拿来生成以及观察低频信号,仅做学习使用 13 | 14 | ### 特性 15 | 16 | 波形发生器部分采用硬件定时器中断进行DAC输出的方式,运行在核心1上。实现方法可见`wave_gen.hpp`文件,里面注释很详细。 17 | 18 | - 波形发生器频率 0-1500Hz 19 | - 波形种类:正弦波、方波、锯齿波 20 | - 可设置偏置电压与峰峰值 21 | - 默认输出引脚 DAC channel 1 即 GPIO25(ESP32) 22 | 23 | 示波器部分是通过i2s进行adc采样后发送给上位机显示的,同样也运行在核心1上。每轮采样2048个点,但只取其中256个,取样间隔可通过设置全局变量`sampleStep`来改变。 24 | 25 | - 示波器采样率,即I2S采样速率,我在代码里限制为1k~550K 26 | - 默认采样引脚是ADC1_CHANNEL_7,即GPIO35 27 | - 取样间隔调整,相当于波形横轴放大,目前最高4倍,适用于观察低频信号 28 | - 软件触发,目前实现了上升沿/下降沿触发 29 | - 示波器更多功能有待继续添加 30 | 31 | 通信方面,ESP32在`websocket_init`函数中初始化了一个websocket服务器以及一个http服务器。与通信相关的程序主要运行在核心0上。 32 | 33 | 这里要感谢来自Vida Wang大佬的[websocket库](https://github.com/vidalouiswang/Arduino_ESP32_Websocket) 34 | 35 | http服务器主要提供静态网页托管功能,可以将存放在flash中的web资源发送给客户端,发送完一次静态资源(280kb)大概需要3~5秒。 36 | 37 | websocket服务器主要用于同客户端建立websocket连接并进行双向通信,客户端通过web界面向ESP32发送指令以更改参数,ESP32则可以主动向客户端发送采样数据及其他信息 38 | 39 | web界面如下所示,其源代码见我的另一个仓库:[ESP32WebScopeUI](https://guohaomeng.coding.net/public/waveform_recognition/ESP32WebScopeUI/git/files) 40 | 41 | ![img1](https://voidtech.cn/i/2022/11/20/vw3dal.png) 42 | 43 | 更新:额外添加了一个50kHz的PWM信号,用于测试I2S+ADC极限采样率下的采样情况,这时候波形跳变较为严重,不过还能看出波形以及正确统计出频率。 44 | 45 | ![img2](https://voidtech.cn/i/2022/11/20/vw3mwh.png) 46 | 47 | ### 使用方法 48 | 49 | 1. 首先克隆本仓库到自己硬盘上,然后用vscode的`platformIO IDE`插件打开 50 | 51 | 2. 然后需要编译并上传data目录下的web资源文件,点击侧边栏的PIO蚂蚁图标,在esp32dev下拉菜单中找到Platform并展开,点击`Build Filesystem Image`根据data目录生成镜像文件,然后连接开发板点击`Upload Filesystem Image`上传到ESP32中。 52 | 53 | 3. 之后便是正常编译下载程序,这个我相信大家肯定都会^_^ 54 | -------------------------------------------------------------------------------- /src/mycrypto/mycrypto.h: -------------------------------------------------------------------------------- 1 | /* 2 | This library able to get sha1/256 and base64 encode/decode using in the 3 | "esp32-arduino" framework. 4 | SHA part is according to offical document of ESP32-WROOM-32(D/E/UE), 5 | using ESP32 hardware acceleration to get sha digest. 6 | If you are using ESP32-C3 or other modules of ESP32, you 7 | should replace those "registers address" to fit in the core, 8 | because of these chip may have different address. 9 | */ 10 | 11 | #ifndef MY_CRYPTO_H_ 12 | #define MY_CRYPTO_H_ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "soc/dport_access.h" 20 | #include "soc/hwcrypto_reg.h" 21 | 22 | #ifndef _DRIVER_PERIPH_CTRL_H_ 23 | #if ESP_IDF_VERSION_MAJOR < 4 24 | #include "esp_private/periph_ctrl.h" 25 | #else 26 | #include "driver/periph_ctrl.h" 27 | #endif 28 | #endif 29 | 30 | #define MY_CRYPTO_DEBUG_HEADER "mycrypto" 31 | 32 | namespace mycrypto 33 | { 34 | typedef enum 35 | { 36 | SHA1 = 1, 37 | SHA256 = 0 38 | } SHAType; 39 | 40 | typedef enum 41 | { 42 | LOWER_CASE, 43 | UPPER_CASE 44 | } SHAOutputCase; 45 | 46 | class SHA 47 | { 48 | private: 49 | static void sha(uint8_t *data, uint64_t length, uint32_t *output, SHAType type = SHA256); 50 | static String aSHA(uint8_t *data, uint64_t length, SHAType type, SHAOutputCase hexCase = LOWER_CASE); 51 | static void convertU32ToU8(uint8_t *data, uint64_t length, uint8_t *output, SHAType type); 52 | 53 | public: 54 | static inline void initialize() 55 | { 56 | periph_module_enable(PERIPH_SHA_MODULE); 57 | } 58 | 59 | static inline void sha1(uint8_t *data, uint64_t length, uint32_t *output) 60 | { 61 | sha(data, length, output, SHA1); 62 | } 63 | 64 | static inline void sha1(uint8_t *data, uint64_t length, uint8_t *output) 65 | { 66 | convertU32ToU8(data, length, output, SHA1); 67 | } 68 | 69 | static inline void sha256(uint8_t *data, uint64_t length, uint8_t *output) 70 | { 71 | convertU32ToU8(data, length, output, SHA256); 72 | } 73 | 74 | static inline void sha256(uint8_t *data, uint64_t length, uint32_t *output) 75 | { 76 | sha(data, length, output, SHA256); 77 | } 78 | 79 | static inline void sha1(uint32_t data, uint8_t *output) 80 | { 81 | uint8_t t[4] = {((uint8_t)(data >> 24)), ((uint8_t)(data >> 16)), ((uint8_t)(data >> 8)), ((uint8_t)(data))}; 82 | convertU32ToU8(t, 4, output, SHA1); 83 | } 84 | 85 | static inline void sha256(uint32_t data, uint8_t *output) 86 | { 87 | uint8_t t[4] = {((uint8_t)(data >> 24)), ((uint8_t)(data >> 16)), ((uint8_t)(data >> 8)), ((uint8_t)(data))}; 88 | convertU32ToU8(t, 4, output, SHA256); 89 | } 90 | 91 | static inline String sha1(uint8_t *data, uint64_t length, SHAOutputCase hexCase = LOWER_CASE) 92 | { 93 | return aSHA(data, length, SHA1, hexCase); 94 | } 95 | 96 | static inline String sha256(uint8_t *data, uint64_t length, SHAOutputCase hexCase = LOWER_CASE) 97 | { 98 | return aSHA(data, length, SHA256, hexCase); 99 | } 100 | 101 | static inline String sha1(String data, SHAOutputCase hexCase = LOWER_CASE) 102 | { 103 | return aSHA((uint8_t *)data.c_str(), data.length(), SHA1, hexCase); 104 | } 105 | 106 | static inline String sha256(String data, SHAOutputCase hexCase = LOWER_CASE) 107 | { 108 | return aSHA((uint8_t *)data.c_str(), data.length(), SHA256, hexCase); 109 | } 110 | 111 | static inline String sha1(String *data, SHAOutputCase hexCase = LOWER_CASE) 112 | { 113 | return aSHA((uint8_t *)data->c_str(), data->length(), SHA1, hexCase); 114 | } 115 | 116 | static inline String sha256(String *data, SHAOutputCase hexCase = LOWER_CASE) 117 | { 118 | return aSHA((uint8_t *)data->c_str(), data->length(), SHA256, hexCase); 119 | } 120 | 121 | static inline String sha1(const char *data, uint64_t length, SHAOutputCase hexCase = LOWER_CASE) 122 | { 123 | return aSHA((uint8_t *)data, length, SHA1, hexCase); 124 | } 125 | 126 | static inline String sha256(const char *data, uint64_t length, SHAOutputCase hexCase = LOWER_CASE) 127 | { 128 | return aSHA((uint8_t *)data, length, SHA256, hexCase); 129 | } 130 | }; 131 | 132 | class Base64 133 | { 134 | private: 135 | static uint8_t getCharIndex(uint8_t c); 136 | static uint8_t *base64Decode(uint8_t *data, uint64_t iLen, uint64_t *oLen); 137 | 138 | public: 139 | static char *base64Encode(uint8_t *data, uint64_t length); 140 | 141 | static inline String base64Encode(const char *data, uint64_t length) 142 | { 143 | char *a = base64Encode((uint8_t *)data, length); 144 | String b = String(a); // this will make a copy in RAM 145 | delete a; 146 | return b; 147 | } 148 | 149 | static inline String base64Encode(String data) 150 | { 151 | return base64Encode((const char *)data.c_str(), data.length()); 152 | } 153 | 154 | static inline uint8_t *base64Decode(std::string data, uint64_t *oLen) 155 | { 156 | return base64Decode((uint8_t *)data.c_str(), data.length() - 1, oLen); 157 | } 158 | 159 | static inline uint8_t *base64Decode(String data, uint64_t *oLen) 160 | { 161 | return base64Decode((uint8_t *)data.c_str(), data.length(), oLen); 162 | } 163 | 164 | static inline String base64Decode(String data) 165 | { 166 | uint64_t oLen = 0; 167 | uint8_t *output = base64Decode((uint8_t *)data.c_str(), data.length(), &oLen); 168 | String a = String((char *)output); // this will make a copy 169 | delete output; 170 | return a; 171 | } 172 | 173 | static inline uint8_t *base64Decode(const char *data, uint64_t iLen, uint64_t *oLen) 174 | { 175 | return base64Decode((uint8_t *)data, iLen, oLen); 176 | } 177 | }; 178 | 179 | } 180 | 181 | #endif -------------------------------------------------------------------------------- /src/i2s_adc.hpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | ****文件路径 : \ESP32WebScope\src\i2s_adc.hpp 3 | ****作者名称 : guohaomeng 4 | ****文件版本 : V1.0.0 5 | ****创建日期 : 2022-07-14 10:43:39 6 | ****简要说明 : 7 | **** 8 | ****版权信息 : 2022 by guohaomeng, All Rights Reserved. 9 | ********************************************************************************/ 10 | #ifndef I2S_ADC_H_ 11 | #define I2S_ADC_H_ 12 | #include 13 | #include 14 | 15 | #define ADC_CHANNEL ADC1_CHANNEL_7 // GPIO35 16 | #define NUM_SAMPLES 2048 17 | 18 | enum TRIGGER_MODE 19 | { 20 | NONE = 1, 21 | UP, 22 | DOWN, 23 | AUTO 24 | }; 25 | 26 | class I2S_ADC 27 | { 28 | public: 29 | i2s_port_t i2s_num = I2S_NUM_0; 30 | adc1_channel_t channel = ADC1_CHANNEL_7; 31 | adc_bits_width_t width_bit = ADC_WIDTH_12Bit; 32 | uint32_t sample_rate = 8000; 33 | uint32_t sample_rate_old = 8000; 34 | bool is_sample = false; 35 | bool is_change_rate = false; 36 | i2s_config_t i2s_config = { 37 | // I2S with ADC 38 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), 39 | .sample_rate = sample_rate, 40 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 41 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, // 双声道模式采集会使实际采样率加倍,且得到的波形噪声更少 42 | .communication_format = I2S_COMM_FORMAT_STAND_I2S, 43 | .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, 44 | .dma_buf_count = 16, 45 | .dma_buf_len = 1024, // 缓冲区大小 = dma_buf_len * chan_num * bits_per_chan / 8 = NUM_SAMPLES * 1 * 16 / 8 = 2048字节 46 | .use_apll = true, // I2S 使用 APLL 作为 I2S 主时钟,使其能够获得准确的时钟 47 | .tx_desc_auto_clear = false, 48 | .fixed_mclk = 0}; 49 | uint16_t i2s_read_buff[NUM_SAMPLES]; // i2s读取缓冲区 50 | float adcBuff[NUM_SAMPLES] = {0}; 51 | float adc_max_value = 0; 52 | float adc_min_value = 0; 53 | TRIGGER_MODE trigger_mode = DOWN; 54 | 55 | I2S_ADC(); 56 | I2S_ADC(i2s_port_t i2s_num, uint32_t sample_rate, adc1_channel_t channel, adc_bits_width_t width_bit); 57 | ~I2S_ADC(); 58 | int get_adc_data(float *po_AdcValues, int length, int step); 59 | void i2s_reset(); 60 | bool set_sample_rate(uint32_t rate); 61 | bool set_trigger_mode(int mode); 62 | int find_trigger_index(int NumSamps); 63 | }; 64 | 65 | /******************************************************************************* 66 | ****函数功能: I2S_ADC默认构造函数 67 | ****出口参数: 无 68 | ****函数备注: 初始化单片机内置I2S_0,并将其连接到ADC1的通道7,即GPIO36 69 | ********************************************************************************/ 70 | I2S_ADC::I2S_ADC() 71 | { 72 | i2s_config_t i2s_config = { 73 | // I2S with ADC 74 | .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX | I2S_MODE_ADC_BUILT_IN), 75 | .sample_rate = (8000), 76 | .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, 77 | .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, 78 | .intr_alloc_flags = 0, 79 | .dma_buf_count = 4, 80 | .dma_buf_len = 1024, 81 | .use_apll = false, 82 | }; 83 | 84 | adc1_config_channel_atten(ADC_CHANNEL, ADC_ATTEN_11db); 85 | adc1_config_width(ADC_WIDTH_12Bit); 86 | i2s_driver_install(I2S_NUM_0, &i2s_config, 0, NULL); 87 | i2s_set_adc_mode(ADC_UNIT_1, ADC_CHANNEL); 88 | i2s_adc_enable(I2S_NUM_0); 89 | } 90 | /******************************************************************************* 91 | ****函数功能: I2S_ADC带参数的构造函数 92 | ****入口参数: i2s_num: i2s端口号 93 | ****入口参数: i2s_config: i2s配置结构体 94 | ****入口参数: channel: 绑定到的adc通道 95 | ****入口参数: width_bit: 采样数据位宽 96 | ****出口参数: 无 97 | ****函数备注: I2S_ADC带参数的构造函数 98 | ********************************************************************************/ 99 | I2S_ADC::I2S_ADC(i2s_port_t i2s_num, uint32_t sample_rate, adc1_channel_t channel, adc_bits_width_t width_bit) 100 | { 101 | this->i2s_num = i2s_num; 102 | this->sample_rate = sample_rate; 103 | this->i2s_config.sample_rate = sample_rate; 104 | this->channel = channel; 105 | this->width_bit = width_bit; 106 | 107 | adc1_config_channel_atten(channel, ADC_ATTEN_11db); 108 | adc1_config_width(width_bit); 109 | i2s_driver_install(i2s_num, &i2s_config, 0, NULL); 110 | i2s_set_adc_mode(ADC_UNIT_1, channel); 111 | i2s_adc_enable(i2s_num); 112 | set_sample_rate(sample_rate); 113 | } 114 | I2S_ADC::~I2S_ADC() {} 115 | /******************************************************************************* 116 | ****函数功能: 获取ADC数据,并将其送入i2s_read_buff数组 117 | ****入口参数: *po_AdcValues:目标数组,adc值读出后放入此数组 118 | ****入口参数: length:目标数组的长度,从adcBuff数组中取出指定长度放入目标数组 119 | ****入口参数: step:放入间隔,在adcBuff数组中每隔step个取出一个放入目标数组 120 | ****出口参数: Numsamps,返回读取的样本数 121 | ****函数备注: 同时刷新对应的电压数组po_AdcValues 122 | ********************************************************************************/ 123 | int I2S_ADC::get_adc_data(float *po_AdcValues, int length, int step) 124 | { 125 | i2s_adc_enable(I2S_NUM_0); 126 | is_sample = true; 127 | size_t num_bytes_read = 0; 128 | i2s_zero_dma_buffer(I2S_NUM_0); 129 | memset(i2s_read_buff, 0x00, NUM_SAMPLES); 130 | memset(adcBuff, 0x00, NUM_SAMPLES); 131 | i2s_read(I2S_NUM_0, &i2s_read_buff, sizeof(i2s_read_buff), &num_bytes_read, (int)(20 * (277000 / sample_rate))); 132 | /* 第一个for循环,数据预处理 */ 133 | int NumSamps = num_bytes_read / (2); 134 | for (int i = 0; i < NumSamps; i++) 135 | { //将12位值转换为电压 136 | adcBuff[i] = 3.3 * (float)((i2s_read_buff[i] & 0x0FFF)) / 0x0FFF; 137 | /* 找出峰峰值 */ 138 | if (adcBuff[i] > adc_max_value) 139 | adc_max_value = adcBuff[i]; 140 | if (adcBuff[i] < adc_min_value) 141 | adc_min_value = adcBuff[i]; 142 | } /* 软件尝试边沿触发 */ 143 | int trigger_index = find_trigger_index(NumSamps); 144 | /* 第二个for循环,获取触发后的波形 */ 145 | for (int i = 0, j = 0; i < NumSamps; i++, j++) 146 | { 147 | if ((j < length) && (i * step) < NumSamps) 148 | { 149 | if (trigger_index != -1) 150 | po_AdcValues[j] = adcBuff[i * step + trigger_index]; 151 | else 152 | po_AdcValues[j] = adcBuff[i * step]; 153 | } 154 | } 155 | is_sample = false; 156 | i2s_adc_disable(I2S_NUM_0); 157 | return NumSamps; // 返回读取的样本数 158 | } 159 | /******************************************************************************* 160 | ****函数功能: 软件触发,找到第一个上升沿/下降沿位置 161 | ****出口参数: index: 第一个上升沿/下降沿位置,-1表示未找到 162 | ****函数备注: 无 163 | ********************************************************************************/ 164 | int I2S_ADC::find_trigger_index(int NumSamps) 165 | { 166 | if (trigger_mode == NONE) 167 | return -1; 168 | uint8_t cmpBuff[NUM_SAMPLES] = {0}; 169 | memset(cmpBuff, 0, NUM_SAMPLES); 170 | int index = -1; 171 | float adc_mid_value = (adc_max_value - adc_min_value) / 2; 172 | for (int i = 0; i < NumSamps; i++) 173 | { 174 | cmpBuff[i] = (adcBuff[i] < adc_mid_value ? 0 : 1); 175 | if (trigger_mode == UP && cmpBuff[i] == 1 && cmpBuff[i - 1] == 0) 176 | { /* (上升沿) */ 177 | if (i <= NUM_SAMPLES / 2) 178 | { /* 如果上升沿的位置在波形的前一半,则可以往后得到完整波形 */ 179 | index = i; 180 | return index; 181 | } 182 | else 183 | { /* 否则判定为触发失败 */ 184 | return -1; 185 | } 186 | } 187 | else if (trigger_mode == DOWN && cmpBuff[i] == 0 && cmpBuff[i - 1] == 1) 188 | { /* (下降沿) */ 189 | if (i <= NUM_SAMPLES / 2) 190 | { /* 如果下降沿的位置在波形的前一半,则可以往后得到完整波形 */ 191 | index = i; 192 | return index; 193 | } 194 | else 195 | { /* 否则判定为触发失败 */ 196 | return -1; 197 | } 198 | } 199 | else if (trigger_mode == AUTO && (abs(cmpBuff[i] - adcBuff[i - 1] == 1))) 200 | { /* (上升沿||下降沿) */ 201 | if (i <= NUM_SAMPLES / 2) 202 | { /* 如果边沿的位置在波形的前一半,则可以往后得到完整波形 */ 203 | index = i; 204 | return index; 205 | } 206 | else 207 | { /* 否则判定为触发失败 */ 208 | return -1; 209 | } 210 | } 211 | } /* 未发现上升沿&下降沿 */ 212 | return -1; 213 | } 214 | /******************************************************************************* 215 | ****函数功能: 重置i2s_adc外设 216 | ****出口参数: 无 217 | ****函数备注: 无 218 | ********************************************************************************/ 219 | void I2S_ADC::i2s_reset() 220 | { 221 | i2s_driver_uninstall(i2s_num); 222 | adc1_config_channel_atten(channel, ADC_ATTEN_11db); 223 | adc1_config_width(width_bit); 224 | i2s_driver_install(i2s_num, &i2s_config, 0, NULL); 225 | i2s_set_adc_mode(ADC_UNIT_1, channel); 226 | i2s_adc_enable(i2s_num); 227 | } 228 | /******************************************************************************* 229 | ****函数功能: 设置示波器采样率 230 | ****入口参数: rate:示波器采样率,实测真实采样率为此值的一半 231 | ****出口参数: true:成功 false:失败 232 | ****函数备注: 似乎最高能到277k?https://github.com/espressif/arduino-esp32/blob/ 233 | master/libraries/ESP32/examples/I2S/HiFreq_ADC/HiFreq_ADC.ino 234 | ********************************************************************************/ 235 | bool I2S_ADC::set_sample_rate(uint32_t rate) 236 | { 237 | if (rate > 550000 || rate < 1000) 238 | { 239 | return false; 240 | } 241 | is_change_rate = true; 242 | if (i2s_config.channel_format <= 2) 243 | rate /= 2; // 左右声道模式采集会导致真实采样率变为设定值的一倍,这里补回来 244 | sample_rate_old = sample_rate; 245 | sample_rate = rate; 246 | esp_err_t ret; 247 | while (1) 248 | { 249 | if (is_sample == false) 250 | { 251 | ret = i2s_set_sample_rates(i2s_num, rate); 252 | break; 253 | } 254 | // 不加延时程序会死循环最后触发看门狗重启 255 | vTaskDelay(2 / portTICK_PERIOD_MS); 256 | } 257 | is_change_rate = false; 258 | // ESP_ERROR_CHECK(i2s_set_sample_rates(i2s_num, rate)); 259 | return (ret == ESP_OK) ? true : false; 260 | } 261 | /******************************************************************************* 262 | ****函数功能: 设置触发模式 263 | ****入口参数: mode: 见TRIGGER_MODE 264 | ****出口参数: 返回结果成功或失败 265 | ****函数备注: 无 266 | ********************************************************************************/ 267 | bool I2S_ADC::set_trigger_mode(int mode) 268 | { 269 | if (mode >= 1 && mode <= 4) 270 | { 271 | trigger_mode = (enum TRIGGER_MODE)(mode); 272 | return true; 273 | } 274 | else 275 | { 276 | Serial.println("触发模式参数错误"); 277 | return false; 278 | } 279 | } 280 | 281 | #endif -------------------------------------------------------------------------------- /src/wave_gen.hpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | ****文件路径 : \ESP32WebScope\src\wave_gen.hpp 3 | ****作者名称 : guohaomeng 4 | ****文件版本 : V1.0.0 5 | ****创建日期 : 2022-07-01 11:43:37 6 | ****简要说明 : 波形发生器类及方法 7 | **** 8 | ****版权信息 : 2022 by guohaomeng, All Rights Reserved. 9 | ********************************************************************************/ 10 | #ifndef WAVE_GEN_H_ 11 | #define WAVE_GEN_H_ 12 | #include 13 | #include "driver/dac.h" 14 | #include "driver/timer.h" 15 | 16 | #define SAMPLE_PER_CYCLE 256 17 | #define ADC_MAX_VALUE 255 18 | #define ADC_MAX_VOLTAGE 3.3f 19 | 20 | enum WAVE_TYPE 21 | { 22 | SIN = 1, 23 | SQUARE, 24 | SAWTOOTH 25 | }; 26 | int waveindex = 2; // 当前波形位置 27 | int wave_index_step = 1; // 波形索引递增步长 28 | uint8_t waveTab[SAMPLE_PER_CYCLE]; // 最终根据配置生成的波形数据 29 | /* 创建硬件定时器 */ 30 | hw_timer_t *timer = NULL; 31 | class WAVE_GEN 32 | { 33 | public: 34 | /* 波形发生器参数配置 */ 35 | double uMaxValue = 3.3; //峰峰值 36 | double offSetValue = 1.65; //偏置电压 37 | int duty = 50; //占空比%(方波) 38 | WAVE_TYPE wave_type = SAWTOOTH; //波形种类 39 | unsigned int freq = 100; // 频率 40 | String param = ""; 41 | 42 | unsigned int freq_old = 100; // 上一次的频率 43 | uint8_t waveTab1[SAMPLE_PER_CYCLE]; // 生成的波形数据 44 | 45 | uint32_t sample_rate = 8000; // i2s_adc的采样速度,仅用于发送 46 | uint8_t sampleStep = 1; // i2s_adc的取样间隔,仅用于发送 47 | uint8_t trigger_mode = 3; // i2s_adc的触发模式,仅用于发送 48 | 49 | /* 波形模式切换按键 */ 50 | const int button = 12; // 波形切换引脚位置 51 | char received_data[5] = {0}; 52 | unsigned int rec_cnt1 = 0; 53 | /* 各种函数,波形发生器相关以及定时器相关的 */ 54 | WAVE_GEN(double uMaxValue, double offSetValue, int duty, unsigned int freq, WAVE_TYPE wave_type); 55 | ~WAVE_GEN(); 56 | int set_uMaxValue(double value); 57 | int set_offSetValue(double value); 58 | int set_duty(int value); 59 | int set_freq(int value); 60 | int set_wave_type(WAVE_TYPE wave_type); 61 | String get_param(); 62 | 63 | void initTimer(); 64 | void updateTimer(); 65 | void get_waveindex(); 66 | void waveSelect(int wave); 67 | void waveGen(WAVE_TYPE wave_type); 68 | void adjust_step(); 69 | }; 70 | 71 | /******************************************************************************* 72 | ****函数功能: 波形发生器构造函数 73 | ****入口参数: uMaxValue:峰峰值 74 | ****入口参数: offSetValue:偏置电压 75 | ****入口参数: duty:占空比 76 | ****入口参数: freq:频率 77 | ****入口参数: wave_type:波形种类 78 | ****出口参数: 无 79 | ****函数备注: 根据以上参数,构造一个波形发生器 80 | ********************************************************************************/ 81 | WAVE_GEN::WAVE_GEN(double uMaxValue, double offSetValue, int duty, unsigned int freq, WAVE_TYPE wave_type) 82 | { 83 | this->uMaxValue = uMaxValue; 84 | this->offSetValue = offSetValue; 85 | this->duty = duty; 86 | this->freq = freq; 87 | this->wave_type = wave_type; 88 | } 89 | 90 | WAVE_GEN::~WAVE_GEN() {} 91 | /******************************************************************************* 92 | ****函数功能: 硬件定时器中断函数 93 | ****出口参数: 无 94 | ****函数备注: 无 95 | ********************************************************************************/ 96 | void IRAM_ATTR onTimer() 97 | { 98 | if (waveindex >= SAMPLE_PER_CYCLE) 99 | { 100 | waveindex = 0; 101 | } 102 | dac_output_voltage(DAC_CHANNEL_1, waveTab[waveindex]); 103 | waveindex += wave_index_step; 104 | } 105 | /******************************************************************************* 106 | ****函数功能: 初始化波形发生器 107 | ****出口参数: 无 108 | ****函数备注: 将25号引脚作为输出,生成默认波形,并配置好定时器 109 | ********************************************************************************/ 110 | void WAVE_GEN::initTimer() 111 | { 112 | /* 输出端口DAC_CHANNEL_1,即GPIO25 */ 113 | dac_output_enable(DAC_CHANNEL_1); 114 | /* 默认输出正弦波 */ 115 | waveGen(wave_type); 116 | 117 | /* 1/(80MHZ/80) = 1us */ 118 | timer = timerBegin(0, 80, true); 119 | Serial.printf("OK"); 120 | /* 将onTimer函数附加到我们的计时器 */ 121 | timerAttachInterrupt(timer, &onTimer, true); 122 | Serial.printf("OK"); 123 | adjust_step(); 124 | Serial.printf("freq:%d\n", freq); 125 | /* 设置闹钟每秒调用onTimer函数1 tick为1us => 1秒为1000000us * / 126 | /* 重复闹钟(第三个参数)*/ 127 | uint64_t T = (1000000 * wave_index_step) / (freq * SAMPLE_PER_CYCLE); 128 | Serial.printf("T:%d\n", T); 129 | timerAlarmWrite(timer, T, true); 130 | 131 | /* 启动定时器 */ 132 | timerAlarmEnable(timer); 133 | Serial.println("定时器启动成功"); 134 | } 135 | /******************************************************************************* 136 | ****函数功能: 更新定时器 137 | ****出口参数: 无 138 | ****函数备注: 在波形发生器频率发生改变后,重设定时器 139 | ********************************************************************************/ 140 | void WAVE_GEN::updateTimer() 141 | { 142 | timerAlarmDisable(timer); //先关闭定时器 143 | adjust_step(); 144 | uint64_t dacTime = (1000000 * wave_index_step) / (freq * SAMPLE_PER_CYCLE); //波形周期,微秒 145 | /* 设置闹钟每秒调用onTimer函数1 tick为1us => 1秒为1000000us */ 146 | /* 重复闹钟(第三个参数)*/ 147 | timerAlarmWrite(timer, dacTime, true); 148 | /* 启动警报 */ 149 | timerAlarmEnable(timer); 150 | } 151 | /******************************************************************************* 152 | ****函数功能: 波形表生成函数 153 | ****入口参数: wave_type:波形种类,有SIN、SQUARE、SAWTOOTH 154 | ****出口参数: 无 155 | ****函数备注: 根据波形发生器的参数生成相应的波形,并将其储存到waveTab[]数组中 156 | ********************************************************************************/ 157 | void WAVE_GEN::waveGen(WAVE_TYPE wave_type) 158 | { 159 | 160 | if (wave_type == SIN) 161 | { 162 | double sineValue = 0.0; 163 | for (int i = 0; i < SAMPLE_PER_CYCLE; i++) 164 | { 165 | sineValue = sin(((2 * PI) / SAMPLE_PER_CYCLE) * i) * (uMaxValue / 2) + offSetValue; 166 | waveTab1[i] = (((int)(sineValue * ADC_MAX_VALUE / ADC_MAX_VOLTAGE))); 167 | } 168 | Serial.printf("波形表重设成功,当前为正弦波\n"); 169 | } 170 | else if (wave_type == SQUARE) 171 | { 172 | float x = SAMPLE_PER_CYCLE * ((float)duty / 100.0); 173 | int x1 = (int)x; 174 | for (int i = 0; i < SAMPLE_PER_CYCLE; i++) 175 | { 176 | if (i < x) 177 | { 178 | waveTab1[i] = (int)(ADC_MAX_VALUE * (uMaxValue / 2 + offSetValue) / ADC_MAX_VOLTAGE); 179 | } 180 | else 181 | { 182 | waveTab1[i] = (int)(ADC_MAX_VALUE * (-(uMaxValue / 2) + offSetValue) / ADC_MAX_VOLTAGE); 183 | } 184 | } 185 | Serial.printf("波形表重设成功,当前为方波,占空比:%d\n", duty); 186 | } 187 | else if (wave_type == SAWTOOTH) //锯齿波 188 | { 189 | for (int i = -(SAMPLE_PER_CYCLE / 2); i < (SAMPLE_PER_CYCLE / 2); i++) 190 | { 191 | waveTab1[i + (SAMPLE_PER_CYCLE / 2)] = (int)(i * (uMaxValue / ADC_MAX_VOLTAGE) + (offSetValue * ADC_MAX_VALUE / ADC_MAX_VOLTAGE)); 192 | } 193 | Serial.println("波形表重设成功,当前为锯齿波"); 194 | } 195 | for (int i = 0; i < SAMPLE_PER_CYCLE; i++) 196 | { 197 | if (waveTab1[i] > ADC_MAX_VALUE) 198 | { 199 | waveTab1[i] = ADC_MAX_VALUE; 200 | } 201 | if (waveTab1[i] < 0) 202 | { 203 | waveTab1[i] = 0; 204 | } 205 | waveTab[i] = (uint8_t)waveTab1[i]; 206 | // Serial.printf("wave:%d\n", waveTab[i]); 207 | } 208 | } 209 | /******************************************************************************* 210 | ****函数功能: 根据频率自动切换跳跃步长 211 | ****出口参数: 无 212 | ****函数备注: 0-100hz时256个数全部依次输出,100-200hz隔一个输出一个,200-300隔2个输出一个··· 213 | ********************************************************************************/ 214 | void WAVE_GEN::adjust_step() 215 | { 216 | if (freq > 0 && freq <= 100) 217 | { 218 | wave_index_step = 1; 219 | } 220 | else if (freq > 100 && freq <= 1500) 221 | { 222 | wave_index_step = (int)(freq / 100) + 1; 223 | } 224 | else 225 | { 226 | } 227 | } 228 | /******************************************************************************* 229 | ****函数功能: 波形切换函数 230 | ****入口参数: wave:波形种类 231 | ****出口参数: 无 232 | ****函数备注: 无 233 | ********************************************************************************/ 234 | void WAVE_GEN::waveSelect(int wave) 235 | { 236 | if (wave < 1 || wave > 3) 237 | { 238 | Serial.println("波形种类设置错误"); 239 | return; 240 | } 241 | wave_type = (enum WAVE_TYPE)(wave); 242 | waveGen(wave_type); 243 | } 244 | /******************************************************************************* 245 | ****函数功能: 设置波形发生器峰峰值 246 | ****入口参数: value:峰峰值大小 247 | ****出口参数: 0:设置成功 -1:超出范围 248 | ****函数备注: 无 249 | ********************************************************************************/ 250 | int WAVE_GEN::set_uMaxValue(double value) 251 | { 252 | if (value >= 0 && value <= 3.3) 253 | { 254 | uMaxValue = value; 255 | waveGen(wave_type); 256 | return 0; 257 | } 258 | else 259 | { 260 | Serial.println("峰峰值设置超出范围0-3.3V"); 261 | return -1; 262 | } 263 | } 264 | /******************************************************************************* 265 | ****函数功能: 设置波形发生器偏置电压 266 | ****入口参数: value:偏置电压大小 267 | ****出口参数: 0:设置成功 -1:超出范围 268 | ****函数备注: 无 269 | ********************************************************************************/ 270 | int WAVE_GEN::set_offSetValue(double value) 271 | { 272 | if (value >= 0 && value <= 3.3) 273 | { 274 | offSetValue = value; 275 | waveGen(wave_type); 276 | return 0; 277 | } 278 | else 279 | { 280 | Serial.println("偏置电压设置超出范围0-3.3V"); 281 | return -1; 282 | } 283 | } 284 | /******************************************************************************* 285 | ****函数功能: 设置波形发生器方波占空比 286 | ****入口参数: value:占空比大小 287 | ****出口参数: 0:设置成功 -1:超出范围 288 | ****函数备注: 无 289 | ********************************************************************************/ 290 | int WAVE_GEN::set_duty(int value) 291 | { 292 | if (value >= 0 && value <= 100) 293 | { 294 | duty = value; 295 | waveGen(wave_type); 296 | return 0; 297 | } 298 | else 299 | { 300 | Serial.println("占空比设置超出范围0-100"); 301 | return -1; 302 | } 303 | } 304 | /******************************************************************************* 305 | ****函数功能: 设置波形发生器信号频率 306 | ****入口参数: value:频率值 307 | ****出口参数: 0:设置成功 -1:超出范围 308 | ****函数备注: 无 309 | ********************************************************************************/ 310 | int WAVE_GEN::set_freq(int value) 311 | { 312 | if (value > 0 && value <= 1500) 313 | { 314 | freq_old = freq; 315 | freq = value; 316 | if (freq != freq_old) 317 | updateTimer(); 318 | return 0; 319 | } 320 | else 321 | { 322 | Serial.println("频率设置超出范围0~1.5kHz"); 323 | return -1; 324 | } 325 | } 326 | /******************************************************************************* 327 | ****函数功能: 获取波形发生器参数 328 | ****出口参数: 无 329 | ****函数备注: json格式,便于上位机解析 330 | ********************************************************************************/ 331 | String WAVE_GEN::get_param() 332 | { 333 | param = ""; 334 | /* 发送如下格式字符串 {"param":{"U":3.3,"B":1.65,"D":50,"F":100,"R":8000,,"S":1,"T":3,"W":1}} */ 335 | param += "{\"param\":{\"U\":"; 336 | param += String(uMaxValue); 337 | param += String(",\"B\":"); 338 | param += String(offSetValue); 339 | param += String(",\"D\":"); 340 | param += String(duty); 341 | param += String(",\"F\":"); 342 | param += String(freq); 343 | param += String(",\"R\":"); 344 | param += String(sample_rate); 345 | param += String(",\"S\":"); 346 | param += String(sampleStep); 347 | param += String(",\"T\":"); 348 | param += String(trigger_mode); 349 | param += String(",\"W\":"); 350 | param += String((int)wave_type); 351 | param += String("}}"); 352 | return param; 353 | } 354 | 355 | #endif -------------------------------------------------------------------------------- /src/mycrypto/mycrypto.cpp: -------------------------------------------------------------------------------- 1 | #include "mycrypto.h" 2 | 3 | namespace mycrypto 4 | { 5 | // universal sha digest function 6 | // use esp32 built-in hardware acceleration module 7 | // all procedures is from Espressif offical technical document 8 | // befor using this function, you need call "periph_module_enable(PERIPH_SHA_MODULE);" at first 9 | // this is included in this class 10 | // call SHA::initialize(); 11 | // or you will got all zero result 12 | void SHA::sha(uint8_t *data, uint64_t length, uint32_t *output, SHAType type) 13 | { 14 | // type 1 for sha1 15 | // 0 for sha256 16 | if (length <= 0) 17 | { 18 | bzero(output, (type & 1 ? 5 : 8)); 19 | return; 20 | } 21 | 22 | // original length 23 | uint64_t ori = length; 24 | 25 | // calc padding length 26 | length = ((length * 8) % 512); 27 | uint64_t zeroLength = ((length < 448) ? (448 - length) : (448 + 512 - length)) / 8; 28 | 29 | // add length 30 | length = ori + zeroLength + 8; 31 | 32 | // allocate buffer 33 | uint8_t *buf = new (std::nothrow) uint8_t[length]; 34 | 35 | if (!buf) 36 | { 37 | output[0] = 0; 38 | return; 39 | } 40 | 41 | // padding zero 42 | bzero(buf, length); 43 | 44 | // copy original data 45 | memcpy(buf, data, ori); 46 | 47 | // padding the "1" after data 48 | buf[ori] = (uint8_t)0x80; 49 | 50 | // add data length(bits) into the tail 51 | uint64_t bits = ori * 8; 52 | for (int i = 0; i < 8; i++) 53 | { 54 | buf[ori + zeroLength + i] = (bits >> ((7 - i) * 8)) & 0xff; 55 | } 56 | 57 | uint64_t i = 0; 58 | 59 | // fill 512 bits(1 block) to start 60 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 0, (uint32_t)((buf[i + 0] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + (buf[i + 3]))); 61 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 4, (uint32_t)((buf[i + 4] << 24) + (buf[i + 5] << 16) + (buf[i + 6] << 8) + (buf[i + 7]))); 62 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 8, (uint32_t)((buf[i + 8] << 24) + (buf[i + 9] << 16) + (buf[i + 10] << 8) + (buf[i + 11]))); 63 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 12, (uint32_t)((buf[i + 12] << 24) + (buf[i + 13] << 16) + (buf[i + 14] << 8) + (buf[i + 15]))); 64 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 16, (uint32_t)((buf[i + 16] << 24) + (buf[i + 17] << 16) + (buf[i + 18] << 8) + (buf[i + 19]))); 65 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 20, (uint32_t)((buf[i + 20] << 24) + (buf[i + 21] << 16) + (buf[i + 22] << 8) + (buf[i + 23]))); 66 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 24, (uint32_t)((buf[i + 24] << 24) + (buf[i + 25] << 16) + (buf[i + 26] << 8) + (buf[i + 27]))); 67 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 28, (uint32_t)((buf[i + 28] << 24) + (buf[i + 29] << 16) + (buf[i + 30] << 8) + (buf[i + 31]))); 68 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 32, (uint32_t)((buf[i + 32] << 24) + (buf[i + 33] << 16) + (buf[i + 34] << 8) + (buf[i + 35]))); 69 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 36, (uint32_t)((buf[i + 36] << 24) + (buf[i + 37] << 16) + (buf[i + 38] << 8) + (buf[i + 39]))); 70 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 40, (uint32_t)((buf[i + 40] << 24) + (buf[i + 41] << 16) + (buf[i + 42] << 8) + (buf[i + 43]))); 71 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 44, (uint32_t)((buf[i + 44] << 24) + (buf[i + 45] << 16) + (buf[i + 46] << 8) + (buf[i + 47]))); 72 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 48, (uint32_t)((buf[i + 48] << 24) + (buf[i + 49] << 16) + (buf[i + 50] << 8) + (buf[i + 51]))); 73 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 52, (uint32_t)((buf[i + 52] << 24) + (buf[i + 53] << 16) + (buf[i + 54] << 8) + (buf[i + 55]))); 74 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 56, (uint32_t)((buf[i + 56] << 24) + (buf[i + 57] << 16) + (buf[i + 58] << 8) + (buf[i + 59]))); 75 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 60, (uint32_t)((buf[i + 60] << 24) + (buf[i + 61] << 16) + (buf[i + 62] << 8) + (buf[i + 63]))); 76 | i += 64; 77 | 78 | // start 79 | if (type & 1) 80 | { 81 | DPORT_REG_WRITE(SHA_1_START_REG, (uint32_t)(1)); 82 | while (DPORT_REG_READ(SHA_1_BUSY_REG)) 83 | { 84 | // yield(); 85 | // because of the hardware acceleration is very fast 86 | // for 8KB data only needs less than 300us(ESPRESSIF YYDS) 87 | // so yield() is no need to call 88 | } 89 | } 90 | else 91 | { 92 | DPORT_REG_WRITE(SHA_256_START_REG, (uint32_t)(1)); 93 | while (DPORT_REG_READ(SHA_256_BUSY_REG)) 94 | { 95 | } 96 | } 97 | 98 | // to process other blocks 99 | // always fill 512bits(a block) at one time 100 | for (; i < length; i += 64) 101 | { 102 | // fill 512 bits into registers to continue 103 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 0, (uint32_t)((buf[i + 0] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + (buf[i + 3]))); 104 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 4, (uint32_t)((buf[i + 4] << 24) + (buf[i + 5] << 16) + (buf[i + 6] << 8) + (buf[i + 7]))); 105 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 8, (uint32_t)((buf[i + 8] << 24) + (buf[i + 9] << 16) + (buf[i + 10] << 8) + (buf[i + 11]))); 106 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 12, (uint32_t)((buf[i + 12] << 24) + (buf[i + 13] << 16) + (buf[i + 14] << 8) + (buf[i + 15]))); 107 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 16, (uint32_t)((buf[i + 16] << 24) + (buf[i + 17] << 16) + (buf[i + 18] << 8) + (buf[i + 19]))); 108 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 20, (uint32_t)((buf[i + 20] << 24) + (buf[i + 21] << 16) + (buf[i + 22] << 8) + (buf[i + 23]))); 109 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 24, (uint32_t)((buf[i + 24] << 24) + (buf[i + 25] << 16) + (buf[i + 26] << 8) + (buf[i + 27]))); 110 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 28, (uint32_t)((buf[i + 28] << 24) + (buf[i + 29] << 16) + (buf[i + 30] << 8) + (buf[i + 31]))); 111 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 32, (uint32_t)((buf[i + 32] << 24) + (buf[i + 33] << 16) + (buf[i + 34] << 8) + (buf[i + 35]))); 112 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 36, (uint32_t)((buf[i + 36] << 24) + (buf[i + 37] << 16) + (buf[i + 38] << 8) + (buf[i + 39]))); 113 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 40, (uint32_t)((buf[i + 40] << 24) + (buf[i + 41] << 16) + (buf[i + 42] << 8) + (buf[i + 43]))); 114 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 44, (uint32_t)((buf[i + 44] << 24) + (buf[i + 45] << 16) + (buf[i + 46] << 8) + (buf[i + 47]))); 115 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 48, (uint32_t)((buf[i + 48] << 24) + (buf[i + 49] << 16) + (buf[i + 50] << 8) + (buf[i + 51]))); 116 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 52, (uint32_t)((buf[i + 52] << 24) + (buf[i + 53] << 16) + (buf[i + 54] << 8) + (buf[i + 55]))); 117 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 56, (uint32_t)((buf[i + 56] << 24) + (buf[i + 57] << 16) + (buf[i + 58] << 8) + (buf[i + 59]))); 118 | DPORT_REG_WRITE(DR_REG_SHA_BASE + 60, (uint32_t)((buf[i + 60] << 24) + (buf[i + 61] << 16) + (buf[i + 62] << 8) + (buf[i + 63]))); 119 | 120 | // continue 121 | if (type & 1) 122 | { 123 | DPORT_REG_WRITE(SHA_1_CONTINUE_REG, (uint32_t)(1)); 124 | 125 | while (DPORT_REG_READ(SHA_1_BUSY_REG)) 126 | { 127 | } 128 | } 129 | else 130 | { 131 | DPORT_REG_WRITE(SHA_256_CONTINUE_REG, (uint32_t)(1)); 132 | 133 | while (DPORT_REG_READ(SHA_256_BUSY_REG)) 134 | { 135 | } 136 | } 137 | } 138 | free(buf); 139 | 140 | // get sha result 141 | if (type & 1) 142 | { 143 | DPORT_REG_WRITE(SHA_1_LOAD_REG, (uint32_t)(1)); 144 | while (DPORT_REG_READ(SHA_1_BUSY_REG)) 145 | { 146 | } 147 | } 148 | else 149 | { 150 | DPORT_REG_WRITE(SHA_256_LOAD_REG, (uint32_t)(1)); 151 | while (DPORT_REG_READ(SHA_256_BUSY_REG)) 152 | { 153 | } 154 | } 155 | 156 | uint8_t shaLen = type & 1 ? 5 : 8; 157 | 158 | // read result 159 | for (int i = 0; i < shaLen; i++) 160 | { 161 | output[i] = (uint32_t)DPORT_REG_READ(DR_REG_SHA_BASE + (i * 4)); 162 | } 163 | } 164 | 165 | // this is for arduino framework 166 | String SHA::aSHA(uint8_t *data, uint64_t length, SHAType type, SHAOutputCase hexCase) 167 | { 168 | // for sha1 is 160 bits which is 5x32 bits 169 | // for sha256 is 256 bits which is 8x32 bits 170 | uint8_t shaLen = type & 1 ? 5 : 8; 171 | uint32_t output[shaLen]; 172 | 173 | // call sha 174 | sha(data, length, output, type); 175 | 176 | // to store formated hex string 177 | char hex[9]; 178 | bzero(hex, 9); 179 | 180 | // return value 181 | String res = ""; 182 | 183 | // format 184 | char format[] = "%08x"; 185 | 186 | // case 187 | if (hexCase == UPPER_CASE) 188 | { 189 | format[3] = 'X'; 190 | } 191 | 192 | // convert result into hex string 193 | for (int i = 0; i < shaLen; i++) 194 | { 195 | sprintf(hex, format, output[i]); 196 | res += hex; 197 | bzero(hex, 9); 198 | } 199 | return res; 200 | } 201 | 202 | void SHA::convertU32ToU8(uint8_t *data, uint64_t length, uint8_t *output, SHAType type) 203 | { 204 | int len = type & 1 ? 5 : 8; 205 | uint32_t o[len]; 206 | sha(data, length, o, type); 207 | int k = 0; 208 | for (int i = 0; i < len; i++) 209 | { 210 | output[k++] = (uint8_t)((o[i] & (uint32_t)(0xff000000)) >> 24); 211 | output[k++] = (uint8_t)((o[i] & (uint32_t)(0x00ff0000)) >> 16); 212 | output[k++] = (uint8_t)((o[i] & (uint32_t)(0x0000ff00)) >> 8); 213 | output[k++] = (uint8_t)(o[i] & (uint32_t)(0x000000ff)); 214 | } 215 | } 216 | 217 | // a very simple base64 encode method 218 | char *Base64::base64Encode(uint8_t *data, uint64_t length) 219 | { 220 | const char *base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 221 | 222 | uint32_t group = 0; 223 | 224 | uint8_t extra = length % 3; 225 | 226 | uint64_t len = length - extra; 227 | 228 | char *res = new (std::nothrow) char[extra == 0 ? (len / 3 * 4 + 1) : (len / 3 * 4 + 5)]{0}; 229 | 230 | uint64_t location = 0; 231 | for (uint64_t i = 0; i < len; i += 3, location += 4) 232 | { 233 | group = (data[i + 0] << 24) + (data[i + 1] << 16) + (data[i + 2] << 8); 234 | 235 | res[location] = base64Table[((uint8_t)(((uint32_t)(4227858432U) & group) >> 26))]; 236 | res[location + 1] = base64Table[((uint8_t)(((uint32_t)(66060288U) & group) >> 20))]; 237 | res[location + 2] = base64Table[((uint8_t)(((uint32_t)(1032192U) & group) >> 14))]; 238 | res[location + 3] = base64Table[((uint8_t)(((uint32_t)(16128U) & group) >> 8))]; 239 | group = 0; 240 | } 241 | 242 | if (extra == 1) 243 | { 244 | res[location] = base64Table[((uint8_t)(data[len] >> 2))]; 245 | res[location + 1] = base64Table[((uint8_t)((data[len] & 3U) << 4))]; 246 | res[location + 2] = '='; 247 | res[location + 3] = '='; 248 | } 249 | else if (extra == 2) 250 | { 251 | uint16_t t = (data[len] << 8) + (data[len + 1]); 252 | res[location] = base64Table[((uint8_t)((uint32_t)(t & 64512U) >> 10))]; 253 | res[location + 1] = base64Table[((uint8_t)((uint32_t)(t & 1008U) >> 4))]; 254 | res[location + 2] = base64Table[((uint8_t)((uint32_t)(t & 15U) << 2))]; 255 | res[location + 3] = '='; 256 | } 257 | 258 | return res; 259 | } 260 | 261 | uint8_t Base64::getCharIndex(uint8_t c) 262 | { 263 | if (c > 96 && c < 123) 264 | { 265 | return c - 97 + 26; 266 | } 267 | else if (c > 64 && c < 91) 268 | { 269 | return c - 65; 270 | } 271 | else if (c > 47 && c < 58) 272 | { 273 | return c - 48 + 52; 274 | } 275 | else if (c == 43) 276 | { 277 | return 62; 278 | } 279 | else if (c == 47) 280 | { 281 | return 63; 282 | } 283 | else 284 | { 285 | return 0; 286 | } 287 | } 288 | 289 | uint8_t *Base64::base64Decode(uint8_t *data, uint64_t iLen, uint64_t *oLen) 290 | { 291 | *oLen = 0; 292 | 293 | if (iLen % 4) 294 | { 295 | return nullptr; 296 | } 297 | 298 | uint64_t eIndex = 0; 299 | 300 | for (int i = 1; i < 3; i++) 301 | { 302 | if (data[iLen - i] == '=') 303 | { 304 | eIndex = i; 305 | } 306 | } 307 | 308 | iLen -= eIndex; 309 | 310 | uint8_t *output = new uint8_t[iLen / 4 * 3 + 3]; 311 | // bzero(output, iLen / 4 * 3 + 2); 312 | 313 | uint8_t tLen = 0; 314 | uint8_t arr[4] = {0}; 315 | 316 | for (uint64_t i = 0; i < iLen; i++) 317 | { 318 | arr[tLen++] = data[i]; 319 | if (tLen >= 4) 320 | { 321 | output[(*oLen)++] = getCharIndex(arr[0]) << 2 | (getCharIndex(arr[1]) & 48U) >> 4; 322 | output[(*oLen)++] = getCharIndex(arr[1]) << 4 | (getCharIndex(arr[2]) & 60U) >> 2; 323 | output[(*oLen)++] = getCharIndex(arr[2]) << 6 | (getCharIndex(arr[3]) & 63U); 324 | tLen = 0; 325 | } 326 | } 327 | if (tLen == 2) 328 | { 329 | output[(*oLen)++] = (getCharIndex(arr[0])) << 2 | (getCharIndex(arr[1])) >> 4; 330 | } 331 | else if (tLen == 3) 332 | { 333 | output[(*oLen)++] = (getCharIndex(arr[0])) << 2 | (getCharIndex(arr[1])) >> 4; 334 | output[(*oLen)++] = (getCharIndex(arr[1])) << 4 | (getCharIndex(arr[2])) >> 2; 335 | } 336 | 337 | output[(*oLen)] = 0; 338 | 339 | return output; 340 | } 341 | 342 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | ****文件路径 : \ESP32WebScope\src\main.cpp 3 | ****作者名称 : guohaomeng 4 | ****文件版本 : V1.0.0 5 | ****创建日期 : 2022-07-01 13:07:26 6 | ****简要说明 : 7 | **** 8 | ****版权信息 : 2022 by guohaomeng, All Rights Reserved. 9 | ********************************************************************************/ 10 | #include 11 | #include "mywebsocket/mywebsocket.h" 12 | #include 13 | #include 14 | #include 15 | 16 | #include "i2s_adc.hpp" 17 | #include "wave_gen.hpp" 18 | 19 | #define FORMAT_SPIFFS_IF_FAILED true 20 | #define ADC_SAMPLE_SIZE 256 21 | float ADC_sample[ADC_SAMPLE_SIZE]; 22 | uint32_t sampleRate = 8000; // 示波器显示的采样频率 = sampleRate / sampleStep 23 | int sampleStep = 1; 24 | bool chart_refresh = false; 25 | 26 | /* 实例化一个波形发生器 */ 27 | WAVE_TYPE wave_type = SAWTOOTH; 28 | WAVE_GEN wave_gen(3.3, 1.65, 50, 100, wave_type); 29 | 30 | /* 初始化基于I2S的ADC,默认采样频率8KHz,每次采样16位,ADC精度12位 */ 31 | I2S_ADC i2s_adc(I2S_NUM_0, sampleRate, ADC1_CHANNEL_7, ADC_WIDTH_12Bit); 32 | 33 | /* 建立http及websocket服务器 */ 34 | myWebSocket::CombinedServer server; 35 | IPAddress APIP = IPAddress(192, 168, 8, 1); 36 | IPAddress subnet = IPAddress(255, 255, 255, 0); 37 | myWebSocket::WebSocketClient *client1 = nullptr; 38 | bool websocket_init(); 39 | void command_loop(void); 40 | void command_loop2(char *received_chars); 41 | int streamFile(const String &filePath, myWebSocket::ExtendedWiFiClient *client, const String &mimeType); 42 | /******************************************************************************* 43 | ****函数功能: 核心0上运行的任务2,运行websocket服务器与http服务器,与上位机通过WiFi进行通信 44 | ****入口参数: *arg: 45 | ****出口参数: 无 46 | ****函数备注: 将主程序与WiFi通信程序分别放两个核心上运行,提高执行速度 47 | ********************************************************************************/ 48 | void Task2(void *arg) 49 | { 50 | int k = 0; 51 | vTaskDelay(50 / portTICK_PERIOD_MS); 52 | while (true) 53 | { 54 | server.loop(); 55 | if (k >= 30) 56 | { 57 | k = 0; 58 | String str = "{\"a\":["; 59 | for (int i = 0; i < ADC_SAMPLE_SIZE - 1; i++) 60 | { 61 | str += (String(ADC_sample[i]) + String(",")); 62 | } 63 | str += (String(ADC_sample[ADC_SAMPLE_SIZE - 1]) + String("]}")); 64 | if (client1 != nullptr && chart_refresh == true) 65 | { /* 向上位机发送数据 */ 66 | client1->send(str); 67 | } 68 | } 69 | k++; 70 | vTaskDelay(10 / portTICK_PERIOD_MS); 71 | } 72 | vTaskDelete(NULL); 73 | } 74 | /******************************************************************************* 75 | ****函数功能: 主程序入口 76 | ****出口参数: 无 77 | ****函数备注: 启动后首先执行setup函数,只执行一次 78 | ********************************************************************************/ 79 | void setup() 80 | { 81 | Serial.begin(115200); 82 | 83 | if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) 84 | { 85 | Serial.println("SPIFFS Mount Failed"); 86 | return; 87 | } 88 | /* 添加一个50kHz的PWM极限测试信号 */ 89 | ledcSetup(5,50000,8); 90 | ledcAttachPin(GPIO_NUM_5,5); 91 | ledcWrite(GPIO_NUM_5,127); 92 | /* 初始化波形发生器 */ 93 | wave_gen.initTimer(); 94 | Serial.println("波形发生器初始化成功"); 95 | websocket_init(); 96 | Serial.println("websocket初始化成功"); 97 | /* 创建任务2,建立并保持与上位机的通信 */ 98 | xTaskCreatePinnedToCore(Task2, "Task2", 12 * 1024, NULL, 1, NULL, 0); 99 | vTaskDelay(50 / portTICK_PERIOD_MS); 100 | } 101 | /******************************************************************************* 102 | ****函数功能: loop函数 103 | ****出口参数: 无 104 | ****函数备注: 程序执行完setup函数后,循环执行loop函数 105 | ********************************************************************************/ 106 | void loop() 107 | { 108 | // 进行一次采样 109 | if (i2s_adc.is_change_rate == false) 110 | i2s_adc.get_adc_data(ADC_sample, ADC_SAMPLE_SIZE, sampleStep); 111 | // for (int i = 0; i < ADC_SAMPLE_SIZE; i++) 112 | // { 113 | // Serial.printf("adc:%d,%.3f\n", i, ADC_sample[i]); 114 | // } 115 | command_loop(); 116 | vTaskDelay(45 / portTICK_PERIOD_MS); 117 | } 118 | 119 | /******************************************************************************* 120 | ****函数功能: websocket服务器及http服务器初始化 121 | ****出口参数: 初始化结果 true/false 122 | ****函数备注: websocket服务器及http服务器初始化,返回初始化结果:成功/失败 123 | ********************************************************************************/ 124 | bool websocket_init() 125 | { 126 | //调用函数启用ESP32硬件加速 127 | mycrypto::SHA::initialize(); 128 | WiFi.softAP("ESP32_WebSocketServer"); 129 | vTaskDelay(300 / portTICK_PERIOD_MS); 130 | WiFi.softAPConfig(APIP, APIP, subnet); 131 | // 设置websocket服务器及回调函数 132 | server.setCallback( 133 | [](myWebSocket::WebSocketClient *client, myWebSocket::WebSocketEvents type, uint8_t *payload, uint64_t length) 134 | { 135 | if (length) 136 | { 137 | if (type == myWebSocket::TYPE_TEXT) 138 | { 139 | char received_chars[15]; 140 | memset(received_chars, '\0', 15); 141 | for (int i = 0; i < length; i++) 142 | { 143 | if (length >= 15) 144 | break; 145 | received_chars[i] = payload[i]; 146 | } 147 | Serial.printf("%s\n", received_chars); 148 | /* 上位机发送指令IDx来建立连接,x标记为指定设备ID */ 149 | if (payload[0] == 'I' && payload[1] == 'D') 150 | { 151 | client->setID(payload[2]); 152 | client1 = client; 153 | } 154 | client->send(String(received_chars)); 155 | command_loop2(received_chars); 156 | } 157 | else if (type == myWebSocket::TYPE_BIN) 158 | { 159 | Serial.println("Got binary data, length: " + String((long)length)); 160 | Serial.println("First byte: " + String(payload[0])); 161 | Serial.println("Last byte: " + String(payload[length - 1])); 162 | } 163 | else if (type == myWebSocket::WS_DISCONNECTED) 164 | { 165 | Serial.println("Websocket disconnected."); 166 | } 167 | else if (type == myWebSocket::WS_CONNECTED) 168 | { 169 | Serial.println("Websocket connected."); 170 | } 171 | } 172 | }); 173 | // 开启网页服务器 174 | server.on( 175 | "/", 176 | [](myWebSocket::ExtendedWiFiClient *client, myWebSocket::HttpMethod method, uint8_t *data, uint64_t len) 177 | { 178 | // 先发送响应头 header 179 | String header = "HTTP/1.1 200 OK\r\n"; 180 | header += (String("Content-Type: text/html;charset=utf-8\r\n")); 181 | header += "Connection: keep-alive\r\n"; 182 | header += "Cache-Control: no-cache\r\n"; 183 | header += "Access-Control-Allow-Origin: *\r\n"; 184 | header += "Transfer-Encoding: chunked\r\n\r\n"; 185 | client->print(header); 186 | client->send(R"( 187 | 188 | 189 | 190 | 191 | 192 | ESP32WebScope 193 | 194 | 195 | 196 | 197 |
198 | 199 | 200 | 201 | )"); 202 | client->close(); 203 | }, 204 | "text/html;charset=utf-8"); 205 | server.on( 206 | "/index.html", 207 | [](myWebSocket::ExtendedWiFiClient *client, myWebSocket::HttpMethod method, uint8_t *data, uint64_t len) 208 | { 209 | streamFile("/index.html.gz", client, "text/html"); 210 | }, 211 | "text/html"); 212 | server.on( 213 | "/css/index.css", 214 | [](myWebSocket::ExtendedWiFiClient *client, myWebSocket::HttpMethod method, uint8_t *data, uint64_t len) 215 | { 216 | streamFile("/css/index.css.gz", client, "text/css"); 217 | }, 218 | "text/css"); 219 | server.on( 220 | "/js/index.js", 221 | [](myWebSocket::ExtendedWiFiClient *client, myWebSocket::HttpMethod method, uint8_t *data, uint64_t len) 222 | { 223 | streamFile("/js/index.js.gz", client, "application/javascript"); 224 | }, 225 | "application/javascript"); 226 | server.begin(80); 227 | return true; 228 | } 229 | 230 | /******************************************************************************* 231 | ****函数功能: 串口通信函数 232 | ****出口参数: 无 233 | ****函数备注: 解析串口接收到的上位机指令 234 | ********************************************************************************/ 235 | void command_loop(void) 236 | { 237 | // 如果串口是空的直接返回 238 | if (Serial.available() == 0) 239 | return; 240 | char received_chars[10]; 241 | memset(received_chars, '\0', 10); 242 | vTaskDelay(1 / portTICK_PERIOD_MS); 243 | // 从串口读取返回的数据,读取20个字符 244 | Serial.read(received_chars, 10); 245 | 246 | // 根据指令做不同动作 247 | if (received_chars[0] == 'F') // F指令设置波形发生器频率 248 | { 249 | int F = atoi(received_chars + 1); 250 | wave_gen.set_freq(F); 251 | Serial.printf("%s,%d\n", received_chars, F); 252 | } 253 | if (received_chars[0] == 'D') // D指令设置波形发生器占空比 254 | { 255 | int D = atoi(received_chars + 1); 256 | wave_gen.set_duty(D); 257 | Serial.printf("%s,%d\n", received_chars, D); 258 | } 259 | if (received_chars[0] == 'U') // U指令设置波形发生器峰峰值 260 | { 261 | double U = atof(received_chars + 1); 262 | wave_gen.set_uMaxValue(U); 263 | Serial.printf("%s,%.3f\n", received_chars, U); 264 | } 265 | if (received_chars[0] == 'B') // B指令设置波形发生器偏置电压 266 | { 267 | double B = atof(received_chars + 1); 268 | wave_gen.set_offSetValue(B); 269 | Serial.printf("%s,%.3f\n", received_chars, B); 270 | } 271 | if (received_chars[0] == 'R') // R指令设置I2S_ADC采样速率 272 | { 273 | int R = atoi(received_chars + 1); 274 | i2s_adc.set_sample_rate((uint32_t)R); 275 | Serial.printf("%s,%d\n", received_chars, R); 276 | } 277 | if (received_chars[0] == 'W') // W指令设置波形种类 278 | { 279 | int W = atoi(received_chars + 1); 280 | wave_gen.waveSelect(W); 281 | } 282 | if (received_chars[0] == 'S') // S指令设置取样间隔 283 | { 284 | int S = atoi(received_chars + 1); 285 | if (S > 0 && S <= 4) 286 | sampleStep = S; 287 | Serial.printf("S,%d\n", S); 288 | } 289 | if (received_chars[0] == 'T') // T指令设置触发模式 290 | { 291 | int T = atoi(received_chars + 1); 292 | i2s_adc.set_trigger_mode(T); 293 | Serial.printf("TriggerMode,%d\n", T); 294 | } 295 | // 最后清空串口 296 | while (Serial.read() >= 0) 297 | ; 298 | } 299 | 300 | /******************************************************************************* 301 | ****函数功能: 解析wifi接收到的上位机指令 302 | ****入口参数: *received_chars:指令所在数组 303 | ****出口参数: 无 304 | ****函数备注: 无 305 | ********************************************************************************/ 306 | void command_loop2(char *received_chars) 307 | { 308 | // 根据指令做不同动作 309 | if (received_chars[0] == 'F') // F指令设置波形发生器频率 310 | { 311 | int F = atoi(received_chars + 1); 312 | wave_gen.set_freq(F); 313 | Serial.printf("%s,%d\n", received_chars, F); 314 | } 315 | if (received_chars[0] == 'D') // D指令设置波形发生器占空比 316 | { 317 | int D = atoi(received_chars + 1); 318 | wave_gen.set_duty(D); 319 | Serial.printf("%s,%d\n", received_chars, D); 320 | } 321 | if (received_chars[0] == 'U') // U指令设置波形发生器峰峰值 322 | { 323 | double U = atof(received_chars + 1); 324 | wave_gen.set_uMaxValue(U); 325 | Serial.printf("%s,%.3f\n", received_chars, U); 326 | } 327 | if (received_chars[0] == 'B') // B指令设置波形发生器偏置电压 328 | { 329 | double B = atof(received_chars + 1); 330 | wave_gen.set_offSetValue(B); 331 | Serial.printf("%s,%.3f\n", received_chars, B); 332 | } 333 | if (received_chars[0] == 'R') // R指令设置I2S_ADC采样速率 334 | { 335 | uint32_t R = atoi(received_chars + 1); 336 | if (i2s_adc.set_sample_rate(R)) 337 | wave_gen.sample_rate = R; 338 | Serial.printf("%s,%d\n", received_chars, R); 339 | } 340 | if (received_chars[0] == 'W') // W指令设置波形 341 | { 342 | int W = atoi(received_chars + 1); 343 | wave_gen.waveSelect(W); 344 | } 345 | if (received_chars[0] == 'S') // S指令设置取样间隔 346 | { 347 | int S = atoi(received_chars + 1); 348 | if (S > 0 && S <= 4){ 349 | sampleStep = S; 350 | wave_gen.sampleStep = sampleStep; 351 | } 352 | Serial.printf("SampleStep,%d\n", S); 353 | } 354 | if (received_chars[0] == 'T') // T指令设置触发模式 355 | { 356 | int T = atoi(received_chars + 1); 357 | if(i2s_adc.set_trigger_mode(T)) 358 | wave_gen.trigger_mode = (int)(i2s_adc.trigger_mode); 359 | Serial.printf("TriggerMode,%d\n", T); 360 | } 361 | if (received_chars[0] == 'C' && received_chars[1] == 'T') 362 | { // CT使能发送采样数据 363 | chart_refresh = true; 364 | } 365 | if (received_chars[0] == 'C' && received_chars[1] == 'F') 366 | { // CF停止发送采样数据 367 | chart_refresh = false; 368 | } 369 | if (received_chars[0] == 'G' && received_chars[1] == 'P') 370 | { // GP指令获取波形发生器全部参数 371 | if (client1 != nullptr) 372 | { /* 向上位机发送数据 */ 373 | client1->send(wave_gen.get_param()); 374 | } 375 | } 376 | } 377 | 378 | /******************************************************************************* 379 | ****函数功能: 根据请求的路径发送文件 380 | ****入口参数: &filePath:SPIFFS系统下的文件路径 381 | ****入口参数: *client:指定的web客户端 382 | ****入口参数: &mimeType:文件类型 383 | ****出口参数: -1:文件不存在 0:发送成功 384 | ****函数备注: 由于index.js.gz文件较大(278K),发送比较容易失败,可以试着多刷新几次 385 | ********************************************************************************/ 386 | int streamFile(const String &filePath, myWebSocket::ExtendedWiFiClient *client, const String &mimeType) 387 | { 388 | File file = SPIFFS.open(filePath, "r"); 389 | if (!file || file.isDirectory()) 390 | { 391 | Serial.println("- failed to open file for reading"); 392 | client->print("HTTP/1.1 404\r\n"); 393 | client->print("Connection: close\r\n"); 394 | client->print("Content-Type: text/plain\r\n"); 395 | client->print("Content-Length: 3\r\n\r\n404"); 396 | client->flush(); 397 | client->stop(); 398 | return -1; 399 | } 400 | /* 先发送响应头 header 401 | * 使用自定义响应头,需要先将 mywebsocket.h中的 autoFillHttpResponseHeader 变量设置为false 402 | * 即关闭自动填充响应头信息的功能 */ 403 | String header = "HTTP/1.1 200 OK\r\n"; 404 | header += (String("Content-Type: ") + mimeType + String("\r\n")); 405 | header += "Connection: keep-alive\r\n"; 406 | if (filePath.endsWith(".gz")) 407 | header += "Content-Encoding: gzip\r\n"; 408 | header += ("Content-Length: " + String(file.size()) + "\r\n"); 409 | header += "Cache-Control: no-cache\r\n"; 410 | header += "Access-Control-Allow-Origin: *\r\n\r\n"; 411 | client->print(header); 412 | /* 空一行再发送响应体 */ 413 | Serial.println(filePath + "," + String(file.size())); 414 | uint64_t len2 = client->write(file); 415 | file.close(); 416 | client->close(); 417 | Serial.printf("writeLen:%d\n", len2); 418 | return 0; 419 | } -------------------------------------------------------------------------------- /src/mywebsocket/mywebsocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | This is a small websocket server/client and http server component. 3 | And is not finished yet. 4 | But currently, it could use for project. 5 | Fast speed, no memory leak. 6 | Designed for ESP32-WROOM-32 work with offical framework arduino-esp32 version 1.0.6. 7 | Because of the default stack size of offical framework is tiny, so code use free store memory as could as possible. 8 | Otherwise your should modify default stack size in sdk config and do some changes with this component. 9 | Websocket client/server don't support wss, because https is not absolute safe either. 10 | With ESP lOT env, I prefer use AES to encrypt data before send it, that much safer than https. 11 | Author: Vida Wang. 12 | */ 13 | 14 | #ifndef MY_WEBSOCKET_H_ 15 | #define MY_WEBSOCKET_H_ 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include "../mycrypto/mycrypto.h" 22 | #include 23 | 24 | #define myWebSocketHeader "GET @PATH@ HTTP/1.1\r\nHost: @HOST@\r\nConnection: Upgrade\r\nSec-WebSocket-Version: 13\r\nCache-Control: no-cache\r\nUpgrade: websocket\r\nSec-WebSocket-Key: @KEY@\r\n\r\n" 25 | 26 | #define DEBUG_MYCRYPTO 1 27 | 28 | #define READ_MAX_TIMES 100 29 | 30 | // payload length 31 | #define MY_WEBSOCKET_CLIENT_MAX_PAYLOAD_LENGTH 102400 32 | 33 | // buffer length for websocket handshake 34 | #define MY_WEBSOCKET_BUFFER_LENGTH 2048 35 | 36 | // http post body length 37 | #define MY_WEBSOCKET_HTTP_POST_LENGTH 2048 38 | 39 | // http header length 40 | #define MY_WEBSOCKET_MAX_HEADER_LENGTH 2048 41 | 42 | // client length 43 | #define MAX_CLIENTS 10 44 | 45 | #define MY_WEBSOCKET_DEBUG_HEADER "myWebsocket" 46 | 47 | namespace myWebSocket 48 | { 49 | // to calc server key 50 | String generateServerKey(String clientKey); 51 | 52 | typedef enum 53 | { 54 | TCP_CONNECTED = 0xfe, 55 | TCP_TIMEOUT = 0xfd, 56 | TCP_FAILED = 0xfc, 57 | WS_CONNECTED = 0xfb, 58 | WS_DISCONNECTED = 0xa8, 59 | TCP_ERROR = 0xf8, 60 | HANDSHAKE_UNKNOWN_ERROR = 0xf7, 61 | MAX_PAYLOAD_EXCEED = 0xf6, 62 | BUFFER_ALLOC_FAILED = 0xf5, 63 | MAX_HEADER_LENGTH_EXCEED = 0xf4, 64 | REACH_MAX_READ_TIMES = 0xae, 65 | TYPE_CON = 0x00, 66 | TYPE_TEXT = 0x01, 67 | TYPE_BIN = 0x02, 68 | TYPE_CLOSE = 0x08, 69 | TYPE_PING = 0x09, 70 | TYPE_PONG = 0x0a, 71 | TYPE_UNKNOWN = 0xff 72 | } WebSocketEvents; 73 | 74 | // websocket client message callback 75 | typedef std::function WebSocketMessageCallback; 76 | 77 | class WebSocketClient 78 | { 79 | private: 80 | String host; 81 | int id = -1; 82 | uint8_t *u8aID = nullptr; 83 | uint16_t port; 84 | String path; // currently only support "/" 85 | bool handShake(); 86 | String domain; 87 | WebSocketMessageCallback fn = nullptr; 88 | WiFiClient *client = nullptr; 89 | 90 | uint8_t *accBuffer = nullptr; 91 | uint64_t accBufferOffset = 0; 92 | 93 | bool autoReconnect = true; 94 | 95 | bool isRecvBufferHasBeenDeleted = false; 96 | 97 | // to store last reconnect time 98 | uint64_t lastConnectTime = 0; 99 | 100 | // reconnect timeout in ms 101 | uint64_t connectTimeout = 5000; 102 | 103 | // for handshake 104 | uint8_t *buffer = new uint8_t[MY_WEBSOCKET_BUFFER_LENGTH]; 105 | 106 | // the local key to send to server 107 | String clientKey; 108 | 109 | String generateHanshake(); 110 | 111 | uint64_t _send(WebSocketEvents type, uint8_t *data, uint64_t len); 112 | 113 | public: 114 | WebSocketEvents status; // to indicate current status of client 115 | 116 | inline WebSocketClient() {} 117 | 118 | // this will be true if this client is transfer from local websocket server 119 | // the client will not reconnect automaticlly if this is true 120 | bool isFromServer = false; 121 | 122 | // only CombinedServer will call this function 123 | // to transfer the client in 124 | inline WebSocketClient(WiFiClient *client) 125 | { 126 | this->client = client; 127 | this->isFromServer = true; 128 | this->status = WS_CONNECTED; 129 | this->autoReconnect = false; 130 | } 131 | 132 | inline ~WebSocketClient() 133 | { 134 | // do clean process 135 | delete this->buffer; 136 | if (this->client != nullptr) 137 | { 138 | delete this->client; 139 | } 140 | } 141 | 142 | inline bool available() 143 | { 144 | return this->client->available(); 145 | } 146 | 147 | inline uint8_t connected() 148 | { 149 | return this->client->connected(); 150 | } 151 | 152 | inline bool connect(String host, uint16_t port = 80, String path = "/") 153 | { 154 | this->host = host; 155 | this->port = port; 156 | this->path = path; 157 | this->status = WS_DISCONNECTED; 158 | return this->handShake(); 159 | } 160 | 161 | bool connect(String url, bool withHeader = false); 162 | 163 | inline bool connect(const char *url, bool withHeader = false) 164 | { 165 | return this->connect(String(url), withHeader); 166 | } 167 | 168 | inline bool connect(const char *host, uint16_t port, const char *path) 169 | { 170 | return this->connect(String(host), port, String(path)); 171 | } 172 | 173 | inline void setCallBack(WebSocketMessageCallback fn) 174 | { 175 | this->fn = fn; 176 | } 177 | 178 | void loop(); 179 | 180 | inline void setRecvBufferDeleted() { this->isRecvBufferHasBeenDeleted = true; } 181 | 182 | inline void setID(uint8_t id) { this->id = id; } 183 | 184 | inline int getID() { return this->id; } 185 | 186 | // fixed 32 bytes SHA256 bytes array 187 | inline void setU8aID(uint8_t *id) 188 | { 189 | // make a copy 190 | for (uint8_t i = 0; i < 32; i++) 191 | this->u8aID[i] = id[i]; 192 | } 193 | 194 | inline uint8_t *getU8aID() 195 | { 196 | return this->u8aID; 197 | } 198 | 199 | // send string 200 | inline uint64_t send(String *data) 201 | { 202 | return this->send(data->c_str()); 203 | } 204 | 205 | // send string 206 | inline uint64_t send(String data) 207 | { 208 | return this->send(data.c_str()); 209 | } 210 | 211 | // send string 212 | uint64_t send(const char *data); 213 | 214 | // send binary 215 | inline uint64_t send(uint8_t *data, uint64_t length) 216 | { 217 | return this->_send(TYPE_BIN, data, length); 218 | } 219 | 220 | // send binary 221 | // this will directly send data as binary 222 | inline uint64_t send(char *data, uint64_t length) 223 | { 224 | return this->_send(TYPE_BIN, (uint8_t *)data, length); 225 | } 226 | 227 | inline void stop() 228 | { 229 | this->autoReconnect = false; 230 | this->client->stop(); 231 | } 232 | inline void disconnect() 233 | { 234 | this->stop(); 235 | } 236 | 237 | inline void setAutoReconnect(bool autoReconnect = true, uint64_t timeout = 5000) 238 | { 239 | this->autoReconnect = autoReconnect; 240 | this->connectTimeout = timeout; 241 | } 242 | }; 243 | 244 | typedef std::function WebSocketClientsCallback; 245 | 246 | // for multiple websocket clients 247 | class WebSocketClients 248 | { 249 | private: 250 | WebSocketClientsCallback fn = nullptr; 251 | WebSocketClient *clients[MAX_CLIENTS] = {nullptr}; 252 | 253 | bool queue(WebSocketClient *client); 254 | 255 | inline WebSocketClient *create() 256 | { 257 | WebSocketClient *client = new WebSocketClient(); 258 | client->setCallBack( 259 | [this, client](WebSocketEvents type, uint8_t *payload, uint64_t length) 260 | { 261 | this->fn(client, type, payload, length); 262 | }); 263 | if (!queue(client)) 264 | { 265 | delete client; 266 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "websocket queue full"); 267 | return nullptr; 268 | } 269 | return client; 270 | } 271 | 272 | public: 273 | inline WebSocketClients() {} 274 | inline WebSocketClients(WebSocketClientsCallback fn) : fn(fn) {} 275 | inline void setCallBack(WebSocketClientsCallback fn) { this->fn = fn; } 276 | inline bool connect(String host, uint16_t port = 80, String path = "/", WebSocketClient *client = nullptr) 277 | { 278 | client = create(); 279 | if (!client) 280 | return false; 281 | return client->connect(host, port, path); 282 | } 283 | 284 | inline bool connect(String url, bool withHeader = false, WebSocketClient *client = nullptr) 285 | { 286 | client = create(); 287 | if (!client) 288 | return false; 289 | return client->connect(url, withHeader); 290 | } 291 | 292 | inline bool connect(const char *url, bool withHeader = false, WebSocketClient *client = nullptr) 293 | { 294 | client = create(); 295 | if (!client) 296 | return false; 297 | return client->connect(url, withHeader); 298 | } 299 | 300 | inline bool connect(const char *host, uint16_t port, const char *path, WebSocketClient *client = nullptr) 301 | { 302 | client = create(); 303 | if (!client) 304 | return false; 305 | return client->connect(String(host), port, String(path)); 306 | } 307 | 308 | // main loop 309 | void loop(); 310 | 311 | // find specific client 312 | WebSocketClient *findByID(uint8_t id); 313 | 314 | // disconnect a client and remove it 315 | bool disconnectAndRemove(WebSocketClient *client); 316 | 317 | inline bool disconnectAndRemove(uint8_t id) 318 | { 319 | return this->disconnectAndRemove(this->findByID(id)); 320 | } 321 | }; 322 | 323 | typedef enum 324 | { 325 | GET, 326 | POST, 327 | NO_METHOD, 328 | OTHERS 329 | } HttpMethod; 330 | 331 | class ExtendedWiFiClient : public WiFiClient 332 | { 333 | public: 334 | inline ExtendedWiFiClient() {} 335 | inline ExtendedWiFiClient(const WiFiClient &externalClient) : WiFiClient(externalClient) {} 336 | inline ~ExtendedWiFiClient() {} 337 | 338 | // this is for default method with "Transfer-Encoding: chunked" 339 | inline uint64_t send(String *content) 340 | { 341 | String *res = new String(content->c_str()); 342 | *res = String(res->length(), HEX) + "\r\n" + *res + "\r\n"; 343 | size_t len = 0; 344 | try 345 | { 346 | len = this->print(*res); 347 | } 348 | catch (std::exception &e) 349 | { 350 | } 351 | 352 | delete res; 353 | return len; 354 | } 355 | 356 | // same as above 357 | inline uint64_t send(const char *content) 358 | { 359 | String *res = new String(content); 360 | auto len = this->send(res); 361 | delete res; 362 | return len; 363 | } 364 | 365 | // send zero block to socket 366 | inline void close() 367 | { 368 | this->print("0\r\n\r\n"); 369 | this->flush(); 370 | this->stop(); 371 | } 372 | }; 373 | 374 | // for universal websocket server msg callback 375 | typedef std::function WebSocketServerCallback; 376 | 377 | // for http handler 378 | typedef std::function NonWebScoketCallback; 379 | 380 | // http callback arguments 381 | typedef struct 382 | { 383 | String path; 384 | int code = 0; 385 | String mimeType; 386 | NonWebScoketCallback fn = nullptr; 387 | } HttpCallback; 388 | 389 | // handle http and websocket request 390 | class CombinedServer 391 | { 392 | private: 393 | // http clients 394 | ExtendedWiFiClient *clients[MAX_CLIENTS] = {nullptr}; 395 | 396 | // websocket clients 397 | WebSocketClient *webSocketClients[MAX_CLIENTS] = {nullptr}; 398 | 399 | WebSocketServerCallback fn = nullptr; 400 | 401 | // router 402 | std::vector nonWebSocketRequests; 403 | 404 | WiFiServer *server; 405 | 406 | // for public post 407 | HttpCallback *publicPostHandler = nullptr; 408 | 409 | // if this set to true you should only provide main content 410 | // of html/js/css... 411 | // otherwise you could process raw data in callback 412 | // edit your own response header or something 413 | bool autoFillHttpResponseHeader = false; 414 | 415 | uint8_t *headerBuffer = new uint8_t[MY_WEBSOCKET_MAX_HEADER_LENGTH]; 416 | int isWebSocketClientArrayHasFreeSapce(); 417 | int isHttpClientArrayHasFreeSpace(); 418 | int findHttpCallback(String path); 419 | void httpHandler(ExtendedWiFiClient *client, String *request); 420 | void newWebSocketClientHandShanke(WiFiClient *client, String request, int index); 421 | 422 | public: 423 | inline CombinedServer() {} 424 | ~CombinedServer(); 425 | inline void setCallback(WebSocketServerCallback fn) 426 | { 427 | this->fn = fn; 428 | } 429 | 430 | // if you'd like process raw data then you 431 | // should call this function at first and set autoFill to false 432 | inline void setAutoFillHttpResponseHeader(bool autoFill) 433 | { 434 | this->autoFillHttpResponseHeader = autoFill; 435 | } 436 | 437 | // for route http requests 438 | inline void on(String path, 439 | NonWebScoketCallback fn, 440 | String mimeType = "text/html;charset=utf-8", 441 | int statusCode = 200, 442 | bool cover = true) 443 | { 444 | this->on(path.c_str(), fn, mimeType.c_str(), cover); 445 | } 446 | 447 | void on(const char *path, 448 | NonWebScoketCallback fn, 449 | const char *mimeType = "text/html;charset=utf-8", 450 | int statusCode = 200, 451 | bool cover = true); 452 | 453 | // handler post data 454 | inline void setPublicPostHandler(NonWebScoketCallback fn) 455 | { 456 | if (!fn) 457 | return; 458 | 459 | if (this->publicPostHandler != nullptr) 460 | { 461 | delete this->publicPostHandler; 462 | } 463 | this->publicPostHandler = new HttpCallback(); 464 | this->publicPostHandler->path = ""; 465 | this->publicPostHandler->fn = fn; 466 | } 467 | 468 | // start server 469 | bool begin(uint16_t port = 80); 470 | 471 | // main loop 472 | void loop(); 473 | 474 | // 新加 475 | // find specific client 476 | WebSocketClient *findByID(uint8_t id); 477 | }; 478 | } // namespace myWebSocket 479 | 480 | #endif -------------------------------------------------------------------------------- /src/mywebsocket/mywebsocket.cpp: -------------------------------------------------------------------------------- 1 | #include "mywebsocket.h" 2 | 3 | namespace myWebSocket 4 | { 5 | 6 | // generate server key by client key 7 | String generateServerKey(String clientKey) 8 | { 9 | int shaLen = 20; 10 | char *output[shaLen]; 11 | 12 | // this part use ESP32 SHA hardware acceleration module directly 13 | // you could change it if you don't use ESP32 14 | mycrypto::SHA::sha1((uint8_t *)clientKey.c_str(), clientKey.length(), (uint8_t *)output); 15 | 16 | #ifdef DEBUG_MYCRYPTO 17 | bool isSHAValid = false; 18 | // SHA result will be all zero if doesn't enable SHA module at first 19 | for (int i = 0; i < shaLen; i++) 20 | { 21 | if (output[i]) 22 | { 23 | isSHAValid = true; 24 | break; 25 | } 26 | } 27 | 28 | if (!isSHAValid) 29 | { 30 | ESP_LOGW(MY_WEBSOCKET_DEBUG_HEADER, "SHA failed, SHA module may not enabled first"); 31 | return String(""); 32 | } 33 | #endif 34 | 35 | return mycrypto::Base64::base64Encode((const char *)output, shaLen); 36 | } 37 | 38 | // make websocket client request headers 39 | String WebSocketClient::generateHanshake() 40 | { 41 | // copy header 42 | String wsHeader = String(myWebSocketHeader); 43 | 44 | // make host 45 | this->domain = String(this->host + ":" + String(this->port)); 46 | 47 | // replace host and path 48 | wsHeader.replace("@HOST@", this->domain); 49 | wsHeader.replace("@PATH@", this->path); 50 | 51 | // generate 16 bytes random key 52 | uint8_t key[16]; 53 | for (uint8_t i = 0; i < 16; i++) 54 | { 55 | key[i] = random(0xFF); 56 | } 57 | 58 | // hash 59 | uint8_t output[20]; 60 | 61 | mycrypto::SHA::sha1(key, 16, output); 62 | 63 | #ifdef DEBUG_MYCRYPTO 64 | bool isSHAValid = false; 65 | for (int i = 0; i < 16; i++) 66 | { 67 | if (output[i]) 68 | { 69 | isSHAValid = true; 70 | break; 71 | } 72 | } 73 | 74 | if (!isSHAValid) 75 | { 76 | ESP_LOGW(MY_WEBSOCKET_DEBUG_HEADER, "SHA failed, SHA module may not enabled first"); 77 | return String(""); 78 | } 79 | #endif 80 | 81 | char *a = mycrypto::Base64::base64Encode(output, 16); 82 | 83 | // store client key 84 | this->clientKey = String(a); // arduino String will make a copy 85 | delete a; 86 | 87 | wsHeader.replace("@KEY@", this->clientKey); 88 | return wsHeader; 89 | } 90 | 91 | bool WebSocketClient::connect(String url, bool withHeader) 92 | { 93 | if (url.startsWith("wss://")) 94 | { 95 | return false; 96 | } 97 | if (url.startsWith("ws://")) 98 | { 99 | url = url.substring(url.indexOf("ws://") + 5); 100 | } 101 | else 102 | { 103 | if (withHeader) 104 | { 105 | return false; 106 | } 107 | } 108 | 109 | int a = url.indexOf(":"); 110 | int b = url.indexOf("/", (a < 0 ? 0 : a)); 111 | 112 | String domain = ""; 113 | uint16_t port = 80; 114 | String path = "/"; 115 | 116 | if (a < 0) 117 | { 118 | if (b < 0) 119 | { // abc.com 120 | domain = url; 121 | } 122 | else 123 | { // abc.com/path 124 | domain = url.substring(0, b); 125 | path = url.substring(b); 126 | } 127 | } 128 | else 129 | { 130 | if (b < 0) 131 | { // abc.com:8080 132 | port = url.substring(a + 1).toInt(); 133 | } 134 | else 135 | { // abc.com:8080/path 136 | port = url.substring(a + 1, b).toInt(); 137 | path = url.substring(b); 138 | } 139 | domain = url.substring(0, a); 140 | } 141 | return this->connect(domain, port, path); 142 | } 143 | 144 | bool WebSocketClient::handShake() 145 | { 146 | this->client = new WiFiClient(); 147 | String header = generateHanshake(); 148 | if (header.isEmpty()) 149 | { 150 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "request header is empty"); 151 | return false; 152 | } 153 | // generate server key to verify after server responsed 154 | String serverKey = this->clientKey + String("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 155 | serverKey = generateServerKey(serverKey); 156 | 157 | // connect to remote server 158 | if (!this->client->connect(this->host.c_str(), this->port)) 159 | { 160 | // connect failed 161 | this->status = TCP_FAILED; 162 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "connect failed"); 163 | if (this->fn) 164 | this->fn(TCP_FAILED, nullptr, 0); 165 | return false; 166 | } 167 | else 168 | { 169 | // connected to remote server 170 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "connected"); 171 | this->client->setNoDelay(1); 172 | this->status = TCP_CONNECTED; 173 | 174 | // write handshake header 175 | uint64_t wroteLen = this->client->print(header); 176 | this->client->flush(); 177 | 178 | if (wroteLen != header.length()) 179 | { 180 | this->status = TCP_ERROR; 181 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "error when send handshake header to server"); 182 | if (this->fn) 183 | this->fn(WS_DISCONNECTED, nullptr, 0); 184 | return false; 185 | } 186 | 187 | while (!this->client->available()) 188 | { 189 | yield(); 190 | } 191 | 192 | // read server response 193 | uint64_t len = this->client->read(this->buffer, MY_WEBSOCKET_BUFFER_LENGTH); 194 | if (len > 0) 195 | { 196 | // add string end 197 | this->buffer[len] = 0; 198 | 199 | // convert to arduino String is more convenient 200 | String handShakeStr = String((char *)this->buffer); 201 | 202 | // check if there has and tail 203 | if (handShakeStr.indexOf("\r\n\r\n") >= 0) 204 | { 205 | // get the server key 206 | int keyStart = handShakeStr.indexOf("Sec-WebSocket-Accept: ") + 22; 207 | int keyEnd = handShakeStr.indexOf("\r\n", keyStart); 208 | String key = handShakeStr.substring(keyStart, keyEnd); 209 | if (key == serverKey && 210 | handShakeStr.indexOf("Upgrade: websocket") >= 0 && 211 | handShakeStr.indexOf("Connection: Upgrade") >= 0 && 212 | handShakeStr.indexOf("HTTP/1.1 101") >= 0) 213 | { 214 | bzero(this->buffer, MY_WEBSOCKET_BUFFER_LENGTH); 215 | this->status = WS_CONNECTED; 216 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "websocket client connected"); 217 | if (this->fn) 218 | this->fn(WS_CONNECTED, nullptr, 0); 219 | return true; 220 | } 221 | else 222 | { 223 | // according to standard should disconnect socket 224 | this->client->stop(); 225 | this->status = HANDSHAKE_UNKNOWN_ERROR; 226 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "handshake failed, response header:(%s)", this->buffer); 227 | if (this->fn) 228 | this->fn(HANDSHAKE_UNKNOWN_ERROR, nullptr, 0); 229 | return false; 230 | } 231 | } 232 | else 233 | { 234 | this->client->stop(); 235 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "handshake failed, response header:(%s)", this->buffer); 236 | this->status = HANDSHAKE_UNKNOWN_ERROR; 237 | if (this->fn) 238 | this->fn(HANDSHAKE_UNKNOWN_ERROR, nullptr, 0); 239 | return false; 240 | } 241 | } 242 | else 243 | { 244 | this->status = TCP_ERROR; 245 | if (this->fn) 246 | this->fn(TCP_ERROR, nullptr, 0); 247 | return false; 248 | } 249 | } 250 | } 251 | 252 | uint64_t WebSocketClient::send(const char *data) 253 | { 254 | uint64_t len = strlen(data); 255 | uint8_t *d = (uint8_t *)malloc(len); 256 | if (!d) 257 | { 258 | return 0; 259 | } 260 | memcpy(d, data, len); 261 | uint64_t r = this->_send(TYPE_TEXT, d, strlen(data)); 262 | free(d); 263 | return r; 264 | } 265 | 266 | uint64_t WebSocketClient::_send(WebSocketEvents type, uint8_t *data, uint64_t len) 267 | { 268 | if (!data || !len) 269 | { 270 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "empty content to send"); 271 | return 0; 272 | } 273 | 274 | uint8_t mask[4]; 275 | for (int i = 0; i < 4; i++) 276 | { 277 | mask[i] = random(0xff); 278 | } 279 | 280 | // frame header 281 | uint8_t header[10]; 282 | bzero(header, 10); 283 | 284 | // fin 285 | header[0] = header[0] | (uint8_t)128; 286 | 287 | // mask 288 | header[1] = this->isFromServer ? 0 : ((uint8_t)128); 289 | 290 | // fill frame type 291 | switch (type) 292 | { 293 | case TYPE_TEXT: 294 | header[0] |= (uint8_t)1; 295 | break; 296 | case TYPE_BIN: 297 | header[0] |= (uint8_t)2; 298 | break; 299 | case TYPE_CLOSE: 300 | header[0] |= (uint8_t)8; 301 | break; 302 | case TYPE_PING: 303 | header[0] |= (uint8_t)9; 304 | break; 305 | case TYPE_PONG: 306 | header[0] |= (uint8_t)10; 307 | break; 308 | default: 309 | return 0; 310 | } 311 | 312 | // convert length and send it to server 313 | if (len < 126) 314 | { 315 | header[1] = header[1] | (uint8_t)len; 316 | this->client->write((const char *)header, 2); 317 | } 318 | else if (len > 125 && len < 65536) 319 | { 320 | header[1] = header[1] | (uint8_t)126; 321 | uint16_t msgLen = (uint16_t)len; 322 | header[2] = (uint8_t)(msgLen >> 8); 323 | header[3] = (uint8_t)((msgLen << 8) >> 8); 324 | this->client->write((const char *)header, 4); 325 | } 326 | else 327 | { 328 | header[1] = header[1] | (uint8_t)127; 329 | uint64_t msgLen = len; 330 | for (int i = 0, j = 2; i < 8; i++, j++) 331 | { 332 | header[j] = (uint8_t)(((msgLen) << (i * 8)) >> 56); 333 | } 334 | this->client->write((const char *)header, 10); 335 | } 336 | 337 | // masking 338 | if (!this->isFromServer) 339 | { 340 | for (uint64_t i = 0; i < len; i++) 341 | { 342 | yield(); 343 | data[i] = data[i] ^ mask[i & 3]; 344 | yield(); 345 | } 346 | } 347 | 348 | // send mask byte 349 | if (!this->isFromServer) 350 | { 351 | this->client->write((const char *)mask, 4); 352 | } 353 | 354 | // send data 355 | return this->client->write((const char *)data, len); 356 | } 357 | 358 | void WebSocketClient::loop() 359 | { 360 | if (this->status == WS_CONNECTED) 361 | { 362 | if (!this->client->connected()) 363 | { 364 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "connection lost"); 365 | 366 | // maual disconnect 367 | this->client->stop(); 368 | 369 | // set statue 370 | this->status = WS_DISCONNECTED; 371 | 372 | // call callback 373 | if (this->fn) 374 | this->fn(WS_DISCONNECTED, nullptr, 0); 375 | 376 | return; 377 | } 378 | 379 | // read frame header 380 | uint64_t len = this->client->read(this->buffer, 2); 381 | 382 | if (!len) // no msg 383 | { 384 | return; 385 | } 386 | 387 | if (len < 0) // socket error 388 | { 389 | this->client->stop(); 390 | 391 | // set statue and call callback 392 | this->status = TCP_ERROR; 393 | if (this->fn) 394 | this->fn(TCP_ERROR, nullptr, 0); 395 | 396 | return; 397 | } 398 | 399 | // msg arived 400 | bool isThisFrameisFin = this->buffer[0] & (uint8_t)128; 401 | 402 | // last frame 403 | WebSocketEvents type; 404 | uint8_t opcode = this->buffer[0] & (uint8_t)15; 405 | switch (opcode) 406 | { 407 | case 0: 408 | type = TYPE_CON; 409 | break; 410 | case 1: 411 | type = TYPE_TEXT; 412 | break; 413 | case 2: 414 | type = TYPE_BIN; 415 | break; 416 | case 9: 417 | type = TYPE_PING; 418 | break; 419 | case 10: 420 | type = TYPE_PONG; 421 | break; 422 | case 8: 423 | type = TYPE_CLOSE; 424 | default: 425 | type = TYPE_UNKNOWN; 426 | this->client->stop(); 427 | this->status = WS_DISCONNECTED; 428 | if (this->fn) 429 | this->fn(WS_DISCONNECTED, nullptr, 0); 430 | return; 431 | } 432 | 433 | // get real length type 434 | uint64_t length = (uint64_t)(this->buffer[1] & (uint8_t)(127)); 435 | 436 | // marked if recv buffer has been deleted by user 437 | this->isRecvBufferHasBeenDeleted = false; 438 | 439 | // declare buffer pointer 440 | uint8_t *buf = nullptr; 441 | uint8_t maskBytes[4]; 442 | 443 | // read real length 444 | if (length > 125) 445 | { 446 | // read real length 447 | bzero(this->buffer, MY_WEBSOCKET_BUFFER_LENGTH); 448 | 449 | // 126: payload length > 125 && payload length < 65536 450 | // 127: > 65535 451 | uint8_t extraPayloadBytes = length == 126 ? 2 : 8; 452 | length = 0; 453 | 454 | // read real length bytes 455 | this->client->read(this->buffer, extraPayloadBytes); 456 | 457 | extraPayloadBytes -= 1; 458 | 459 | // convert to uint64_t 460 | // for front byte 461 | for (uint8_t i = 0; i < extraPayloadBytes; i++) 462 | { 463 | length += this->buffer[i]; 464 | length = length << 8; 465 | } 466 | // for last byte 467 | length += this->buffer[extraPayloadBytes]; 468 | 469 | // see if beyond max length 470 | if (extraPayloadBytes > MY_WEBSOCKET_CLIENT_MAX_PAYLOAD_LENGTH) 471 | { 472 | this->status = MAX_PAYLOAD_EXCEED; 473 | if (this->fn) 474 | this->fn(MAX_PAYLOAD_EXCEED, nullptr, 0); 475 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "MAX_PAYLOAD_EXCEED"); 476 | return; 477 | } 478 | } 479 | 480 | // define another length for optimze unmask process 481 | uint64_t bufferLength = length; 482 | 483 | // length +1 for text type end of string 484 | if (type == TYPE_TEXT && isThisFrameisFin) 485 | { 486 | // for '\0' if there isn't '\0' at tail 487 | bufferLength += 1; 488 | } 489 | 490 | // for optimize unmask process 491 | bufferLength += 4 - (bufferLength % 4); 492 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "optimized length:%d", bufferLength); 493 | 494 | // if length overflow it will be 0(though it won't happen forever on esp32 because of limited RAM size) 495 | if (!bufferLength || this->accBufferOffset + length > MY_WEBSOCKET_CLIENT_MAX_PAYLOAD_LENGTH) 496 | { 497 | this->status = MAX_PAYLOAD_EXCEED; 498 | if (this->fn) 499 | this->fn(MAX_PAYLOAD_EXCEED, nullptr, 0); 500 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "MAX_PAYLOAD_EXCEED"); 501 | return; 502 | } 503 | 504 | // allocate buffer 505 | buf = new (std::nothrow) uint8_t[bufferLength]; 506 | 507 | // check buffer allocate success or not 508 | if (buf == nullptr) 509 | { 510 | this->status = BUFFER_ALLOC_FAILED; 511 | if (this->fn) 512 | this->fn(BUFFER_ALLOC_FAILED, nullptr, 0); 513 | return; 514 | } 515 | 516 | // if this client is transfer from server 517 | // that means 4 bytes mask key should read at first 518 | if (this->isFromServer) 519 | { 520 | this->client->read(maskBytes, 4); 521 | } 522 | 523 | // otherwise this client is directly connected to remote 524 | // no mask key should read 525 | uint64_t readLength = this->client->read(buf, length); 526 | 527 | // read times 528 | int times = 0; 529 | 530 | // single read times 531 | int segmentLength = 0; 532 | 533 | // read data 534 | while (readLength < length && ++times < READ_MAX_TIMES) 535 | { 536 | while (!this->client->available()) 537 | { 538 | yield(); 539 | delay(1); 540 | } 541 | 542 | // read more data 543 | segmentLength = this->client->read(buf + readLength, length - readLength); 544 | 545 | // no more data to read 546 | if (!segmentLength) 547 | { 548 | break; 549 | } 550 | 551 | // accumulate read length 552 | readLength += segmentLength; 553 | 554 | // reset single time read length 555 | segmentLength = 0; 556 | 557 | if (times > READ_MAX_TIMES) 558 | { 559 | if (buf) 560 | delete buf; 561 | 562 | this->client->stop(); 563 | this->status = REACH_MAX_READ_TIMES; 564 | 565 | if (this->fn) 566 | this->fn(REACH_MAX_READ_TIMES, nullptr, 0); 567 | 568 | return; 569 | } 570 | } 571 | 572 | if (readLength == length) 573 | { 574 | // correct data length 575 | if (this->isFromServer) 576 | { 577 | // unmask 578 | // this will consume 2200us if length type is uint64_t with 240MHz cpu config 579 | // data size 64KB, AP mode 580 | for (uint64_t i = 0; i ^ bufferLength; i += 4) 581 | { 582 | buf[i] = buf[i] ^ maskBytes[i & 3]; 583 | buf[(i + 1)] = buf[(i + 1)] ^ maskBytes[(i + 1) & 3]; 584 | buf[(i + 2)] = buf[(i + 2)] ^ maskBytes[(i + 2) & 3]; 585 | buf[(i + 3)] = buf[(i + 3)] ^ maskBytes[(i + 3) & 3]; 586 | } 587 | 588 | // set extra tail zero 589 | memset(buf + length, 0, bufferLength - length); 590 | } 591 | 592 | // copy data to extra buffer if this frame isn't last frame 593 | // otherwise call callback 594 | if (isThisFrameisFin) 595 | { 596 | // call handler 597 | if (this->accBufferOffset) 598 | { 599 | // copy last chunk 600 | memcpy(this->accBuffer + this->accBufferOffset, buf, length); 601 | this->accBufferOffset += length; 602 | 603 | if (this->fn) 604 | this->fn(type, this->accBuffer, this->accBufferOffset); 605 | 606 | if (!this->isRecvBufferHasBeenDeleted) 607 | { 608 | delete this->accBuffer; 609 | } 610 | this->accBufferOffset = 0; 611 | } 612 | else 613 | { 614 | if (this->fn) 615 | this->fn(type, buf, length); 616 | } 617 | } 618 | else 619 | { 620 | // define extra buffer 621 | if (!this->accBufferOffset) 622 | { 623 | this->accBuffer = new (std::nothrow) uint8_t[MY_WEBSOCKET_CLIENT_MAX_PAYLOAD_LENGTH]; 624 | 625 | //check extra buffer allocate state 626 | if (!(this->accBuffer)) 627 | { 628 | if (buf != nullptr) 629 | { 630 | delete buf; 631 | } 632 | 633 | this->status = BUFFER_ALLOC_FAILED; 634 | if (this->fn) 635 | this->fn(BUFFER_ALLOC_FAILED, nullptr, 0); 636 | 637 | return; 638 | } 639 | } 640 | 641 | //copy original buffer 642 | memcpy(this->accBuffer + this->accBufferOffset, buf, length); 643 | 644 | //accumulate extra buffer offset 645 | this->accBufferOffset += length; 646 | } 647 | } 648 | else 649 | { 650 | this->client->stop(); 651 | this->status = TCP_ERROR; 652 | if (this->fn) 653 | this->fn(TCP_ERROR, nullptr, 0); 654 | } 655 | 656 | if (!(this->isRecvBufferHasBeenDeleted)) 657 | { 658 | if (buf) 659 | { 660 | delete buf; 661 | } 662 | } 663 | } 664 | else 665 | { 666 | // disconnected 667 | if (this->autoReconnect && !this->isFromServer) 668 | { 669 | if (millis() - this->lastConnectTime > this->connectTimeout) 670 | { 671 | this->lastConnectTime = millis(); 672 | if (this->client != nullptr) 673 | { 674 | this->client->stop(); 675 | delete this->client; 676 | this->client = nullptr; 677 | } 678 | this->handShake(); 679 | } 680 | } 681 | } 682 | } 683 | 684 | bool WebSocketClients::queue(WebSocketClient *client) 685 | { 686 | for (uint8_t i = 0; i < MAX_CLIENTS; i++) 687 | { 688 | if (this->clients[i] == nullptr) 689 | { 690 | this->clients[i] = client; 691 | return true; 692 | } 693 | } 694 | return false; 695 | } 696 | 697 | void WebSocketClients::loop() 698 | { 699 | for (uint8_t i = 0; i < MAX_CLIENTS; i++) 700 | { 701 | if (this->clients[i] != nullptr) 702 | { 703 | this->clients[i]->loop(); 704 | } 705 | } 706 | } 707 | 708 | WebSocketClient *WebSocketClients::findByID(uint8_t id) 709 | { 710 | for (uint8_t i = 0; i < MAX_CLIENTS; i++) 711 | { 712 | if (this->clients[i] != nullptr) 713 | { 714 | if (this->clients[i]->getID() == id) 715 | { 716 | return this->clients[i]; 717 | } 718 | } 719 | } 720 | return nullptr; 721 | } 722 | 723 | bool WebSocketClients::disconnectAndRemove(WebSocketClient *client) 724 | { 725 | if (!client) 726 | return false; 727 | for (uint8_t i = 0; i < MAX_CLIENTS; ++i) 728 | { 729 | if (this->clients[i] == client) 730 | { 731 | this->clients[i]->stop(); 732 | delete this->clients[i]; 733 | this->clients[i] = nullptr; 734 | return true; 735 | } 736 | } 737 | return false; 738 | } 739 | 740 | bool CombinedServer::begin(uint16_t port) 741 | { 742 | this->server = new WiFiServer(port, 100); 743 | this->server->setNoDelay(true); 744 | this->server->begin(); 745 | return true; 746 | } 747 | 748 | int CombinedServer::isWebSocketClientArrayHasFreeSapce() 749 | { 750 | for (int i = 0; i < MAX_CLIENTS; i++) 751 | { 752 | if (nullptr == this->webSocketClients[i]) 753 | { 754 | return i; 755 | break; 756 | } 757 | } 758 | return -1; 759 | } 760 | 761 | int CombinedServer::isHttpClientArrayHasFreeSpace() 762 | { 763 | for (int i = 0; i < MAX_CLIENTS; i++) 764 | { 765 | if (nullptr == this->clients[i]) 766 | { 767 | return i; 768 | break; 769 | } 770 | } 771 | return -1; 772 | } 773 | 774 | void CombinedServer::newWebSocketClientHandShanke(WiFiClient *client, String request, int index) 775 | { 776 | // websocket 777 | int keyStart = request.indexOf("Sec-WebSocket-Key: "); // + 19; 778 | if (keyStart < 0) 779 | { 780 | // client->print("HTTP/1.1 403\r\n\r\n"); 781 | // client->flush(); 782 | client->stop(); 783 | return; 784 | } 785 | 786 | // length of "Sec-WebSocket-Key: " 787 | keyStart += 19; 788 | 789 | // to generate server key 790 | String clientKey = request.substring(keyStart, request.indexOf("\r\n", keyStart)); 791 | 792 | String serverKey = clientKey + String("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); 793 | serverKey = generateServerKey(serverKey); 794 | 795 | String response = "HTTP/1.1 101 Switching Protocols\r\n"; 796 | response += "Connection: upgrade\r\n"; 797 | response += "Upgrade: websocket\r\n"; 798 | response += "Content-Length: 0\r\n"; 799 | response += "Sec-WebSocket-Accept: " + serverKey + "\r\n\r\n"; 800 | 801 | // give response to client 802 | client->print(response); 803 | client->flush(); 804 | 805 | // transfer to a object of WebSocketClient 806 | WebSocketClient *webSocketClient = new WebSocketClient(client); 807 | 808 | // set callback 809 | webSocketClient->setCallBack( 810 | [this, webSocketClient](WebSocketEvents type, uint8_t *payload, uint64_t length) 811 | { 812 | // here don't process any events 813 | // all events will push to callback 814 | if (this->fn) 815 | this->fn(webSocketClient, type, payload, length); 816 | }); 817 | 818 | // push websocket client into queue 819 | this->webSocketClients[index] = webSocketClient; 820 | } 821 | // 新加 822 | WebSocketClient *CombinedServer::findByID(uint8_t id) 823 | { 824 | for (uint8_t i = 0; i < MAX_CLIENTS; i++) 825 | { 826 | if (this->webSocketClients[i] != nullptr) 827 | { 828 | if (this->webSocketClients[i]->getID() == id) 829 | { 830 | return this->webSocketClients[i]; 831 | } 832 | } 833 | } 834 | return nullptr; 835 | } 836 | int CombinedServer::findHttpCallback(String path) 837 | { 838 | if (this->nonWebSocketRequests.size() <= 0) 839 | { 840 | return -1; 841 | } 842 | 843 | for (int i = 0; i < this->nonWebSocketRequests.size(); i++) 844 | { 845 | if (path == this->nonWebSocketRequests.at(i)->path) 846 | { 847 | return i; 848 | } 849 | } 850 | return -1; 851 | } 852 | 853 | void CombinedServer::httpHandler(ExtendedWiFiClient *client, String *request) 854 | { 855 | // from http handshake 856 | if (request != nullptr) 857 | { 858 | if (request->length() > 0) 859 | { 860 | HttpMethod method; 861 | int pathStart = request->indexOf("GET ", 0); // + 4; 862 | 863 | if (pathStart < 0) 864 | { 865 | pathStart = request->indexOf("POST ", 0); // + 5; 866 | if (pathStart < 0) 867 | { 868 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "can not find path of http request"); 869 | return; 870 | } 871 | pathStart += 5; 872 | method = POST; 873 | } 874 | else 875 | { 876 | pathStart += 4; 877 | method = GET; 878 | } 879 | 880 | int pathEnd = request->indexOf(" HTTP", pathStart); 881 | 882 | if (pathStart >= pathEnd) 883 | { 884 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "can not find path of http request"); 885 | return; 886 | } 887 | 888 | // get path 889 | String path = request->substring(pathStart, pathEnd); 890 | 891 | if (path.length()) 892 | { 893 | // find GET process callback 894 | int index = this->findHttpCallback(path); 895 | 896 | if (index >= 0) 897 | { 898 | if (this->autoFillHttpResponseHeader) 899 | { 900 | // fill header 901 | String mimeTypeTmp = String(this->nonWebSocketRequests.at(index)->mimeType); 902 | String res = "HTTP/1.1 " + String(this->nonWebSocketRequests.at(index)->code ? this->nonWebSocketRequests.at(index)->code : 200) + " OK\r\n"; 903 | res += "Content-Type: " + mimeTypeTmp + "\r\n"; 904 | res += "Connection: keep-alive\r\n"; 905 | if(path.endsWith(".gz")) 906 | res += "Content-Encoding: gzip\r\n"; 907 | res += "Cache-Control: no-cache\r\n"; 908 | res += "Access-Control-Allow-Origin: *\r\n"; 909 | res += "Transfer-Encoding: chunked\r\n\r\n"; 910 | client->print(res); 911 | } 912 | 913 | if (this->nonWebSocketRequests.at(index)->fn) 914 | this->nonWebSocketRequests.at(index)->fn(client, method, (uint8_t *)request, request->length()); 915 | } 916 | else 917 | { 918 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "no http handler mathced: %s", path.c_str()); 919 | client->print("HTTP/1.1 404\r\n"); 920 | client->print("Connection: close\r\n"); 921 | client->print("Content-Type: text/plain\r\n"); 922 | client->print("Content-Length: 3\r\n\r\n404"); 923 | client->flush(); 924 | client->stop(); 925 | } 926 | } 927 | else 928 | { 929 | client->stop(); 930 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "empty http request path"); 931 | } 932 | return; 933 | } 934 | else 935 | { 936 | client->stop(); 937 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "request is empty"); 938 | } 939 | } 940 | 941 | // from loop 942 | if (this->publicPostHandler == nullptr) 943 | { 944 | return; 945 | } 946 | 947 | uint8_t *data = new (std::nothrow) uint8_t[MY_WEBSOCKET_HTTP_POST_LENGTH]; 948 | 949 | if (!data) 950 | { 951 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "memory full"); 952 | 953 | if (this->fn) 954 | this->fn(nullptr, BUFFER_ALLOC_FAILED, nullptr, 0); 955 | } 956 | 957 | long len = client->read(data, MY_WEBSOCKET_HTTP_POST_LENGTH); 958 | 959 | if (len < 0) 960 | { 961 | // socket error 962 | client->stop(); 963 | if (this->fn) 964 | this->fn(nullptr, TCP_ERROR, nullptr, 0); 965 | } 966 | else 967 | { 968 | // empty content 969 | return; 970 | } 971 | 972 | try 973 | { 974 | this->publicPostHandler->fn(client, NO_METHOD, data, len); 975 | } 976 | catch (std::exception &e) 977 | { 978 | delete data; 979 | data = nullptr; 980 | } 981 | 982 | if (data != nullptr) 983 | { 984 | delete data; 985 | } 986 | } 987 | 988 | void CombinedServer::loop() 989 | { 990 | if (this->server->hasClient()) 991 | { 992 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "A new client connected"); 993 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "Free Heap: %d", ESP.getFreeHeap()); 994 | 995 | // get client 996 | WiFiClient newClient = this->server->available(); 997 | 998 | // store fd 999 | ExtendedWiFiClient *client = new ExtendedWiFiClient(newClient); 1000 | client->setNoDelay(true); 1001 | 1002 | // for process request data 1003 | String request = ""; 1004 | 1005 | if (client->available()) 1006 | { 1007 | // read to uint8_t buffer is faster than arduino String 1008 | bzero(this->headerBuffer, MY_WEBSOCKET_MAX_HEADER_LENGTH); 1009 | int len = client->read(this->headerBuffer, MY_WEBSOCKET_MAX_HEADER_LENGTH); 1010 | if (len < 0) 1011 | { 1012 | client->stop(); 1013 | delete client; 1014 | 1015 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "error when read request header"); 1016 | if (this->fn) 1017 | this->fn(nullptr, TCP_ERROR, nullptr, 0); 1018 | 1019 | return; 1020 | } 1021 | 1022 | if (len >= MY_WEBSOCKET_MAX_HEADER_LENGTH) 1023 | { 1024 | client->stop(); 1025 | delete client; 1026 | 1027 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "request header length beyond max length"); 1028 | 1029 | if (this->fn) 1030 | this->fn(nullptr, MAX_HEADER_LENGTH_EXCEED, nullptr, 0); 1031 | 1032 | return; 1033 | } 1034 | 1035 | this->headerBuffer[len++] = 0; 1036 | 1037 | // convert to arduino String 1038 | request = String((char *)this->headerBuffer); 1039 | 1040 | memset(this->headerBuffer + len, 0xff, MY_WEBSOCKET_MAX_HEADER_LENGTH - len); 1041 | } 1042 | else 1043 | { 1044 | return; 1045 | } 1046 | 1047 | if (request.length() > 0) 1048 | { 1049 | if (request.indexOf("\r\n\r\n") >= 0) 1050 | { 1051 | if ( 1052 | request.indexOf("Connection: Upgrade") >= 0 && 1053 | request.indexOf("Upgrade: websocket") >= 0 && 1054 | request.indexOf("Sec-WebSocket-Key") >= 0) 1055 | { 1056 | // websocket request 1057 | int index = this->isWebSocketClientArrayHasFreeSapce(); 1058 | if (index < 0) 1059 | { 1060 | // queue full 1061 | client->print("HTTP/1.1 404\r\nError: queue-full\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); 1062 | client->flush(); 1063 | client->stop(); 1064 | delete client; 1065 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "websocket queue full"); 1066 | } 1067 | else 1068 | { 1069 | // process websocket request 1070 | this->newWebSocketClientHandShanke(client, request, index); 1071 | } 1072 | } 1073 | else 1074 | { 1075 | // http request 1076 | int index = this->isHttpClientArrayHasFreeSpace(); 1077 | if (index >= 0) 1078 | { 1079 | this->clients[index] = client; 1080 | this->httpHandler(client, &request); 1081 | } 1082 | else 1083 | { 1084 | client->print("HTTP/1.1 404\r\nError: queue-full\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); 1085 | client->flush(); 1086 | client->stop(); 1087 | delete client; 1088 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "http queue full"); 1089 | } 1090 | } 1091 | } 1092 | } 1093 | } 1094 | 1095 | // loop websocket clients 1096 | WebSocketClient *loopClient = nullptr; 1097 | 1098 | // loop http clients 1099 | ExtendedWiFiClient *extendedclient = nullptr; 1100 | for (int i = 0; i < MAX_CLIENTS; i++) 1101 | { 1102 | // websocket 1103 | loopClient = this->webSocketClients[i]; 1104 | if (nullptr != loopClient) 1105 | { 1106 | if (loopClient->connected()) 1107 | { 1108 | if (loopClient->available()) 1109 | { 1110 | loopClient->loop(); 1111 | } 1112 | } 1113 | else 1114 | { 1115 | delete loopClient; 1116 | this->webSocketClients[i] = nullptr; 1117 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "A websocket client disconnected"); 1118 | } 1119 | } 1120 | 1121 | // others 1122 | extendedclient = this->clients[i]; 1123 | if (nullptr != extendedclient) 1124 | { 1125 | if (extendedclient->connected()) 1126 | { 1127 | if (extendedclient->available()) 1128 | { 1129 | this->httpHandler(extendedclient, nullptr); 1130 | } 1131 | } 1132 | else 1133 | { 1134 | delete extendedclient; 1135 | this->clients[i] = nullptr; 1136 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "A client disconnected"); 1137 | } 1138 | } 1139 | } 1140 | } 1141 | 1142 | void CombinedServer::on(const char *path, NonWebScoketCallback fn, const char *mimeType, int statusCode, bool cover) 1143 | { 1144 | HttpCallback *cb = new HttpCallback(); 1145 | cb->path = String(path); 1146 | cb->code = statusCode; 1147 | cb->mimeType = String(mimeType); 1148 | cb->fn = fn; 1149 | 1150 | if (cb->path.isEmpty() || !fn) 1151 | { 1152 | delete cb; 1153 | ESP_LOGD(MY_WEBSOCKET_DEBUG_HEADER, "invalid path or callback for request"); 1154 | return; 1155 | } 1156 | 1157 | bool stored = false; 1158 | for ( 1159 | std::vector::iterator it = this->nonWebSocketRequests.begin(); 1160 | it != this->nonWebSocketRequests.end(); 1161 | it++) 1162 | { 1163 | if ((*it)->path == cb->path) 1164 | { 1165 | if (cover) 1166 | { 1167 | (*it)->fn = fn; 1168 | delete cb; 1169 | break; 1170 | } 1171 | else 1172 | { 1173 | delete (*it); 1174 | this->nonWebSocketRequests.push_back(cb); 1175 | break; 1176 | } 1177 | stored = true; 1178 | } 1179 | } 1180 | 1181 | if (!stored) 1182 | { 1183 | this->nonWebSocketRequests.push_back(cb); 1184 | } 1185 | } 1186 | 1187 | CombinedServer::~CombinedServer() 1188 | { 1189 | // do some clean process 1190 | 1191 | // delete header buffer 1192 | if (this->headerBuffer != nullptr) 1193 | { 1194 | delete this->headerBuffer; 1195 | } 1196 | 1197 | // delete post handler 1198 | if (this->publicPostHandler != nullptr) 1199 | { 1200 | delete this->publicPostHandler; 1201 | } 1202 | 1203 | // delete http callbacks 1204 | if (this->nonWebSocketRequests.size() > 0) 1205 | { 1206 | for (int i = 0; i < this->nonWebSocketRequests.size(); i++) 1207 | { 1208 | delete this->nonWebSocketRequests.at(i); 1209 | } 1210 | } 1211 | 1212 | // stop all clients and delete them 1213 | for (int i = 0; i < MAX_CLIENTS; i++) 1214 | { 1215 | if (nullptr != this->clients[i]) 1216 | { 1217 | this->clients[i]->stop(); 1218 | delete this->clients[i]; 1219 | } 1220 | if (nullptr != this->webSocketClients[i]) 1221 | { 1222 | this->webSocketClients[i]->stop(); 1223 | delete this->webSocketClients[i]; 1224 | } 1225 | } 1226 | 1227 | // release vector memory 1228 | std::vector().swap(this->nonWebSocketRequests); 1229 | } 1230 | }; 1231 | --------------------------------------------------------------------------------