├── 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 | 
42 |
43 | 更新:额外添加了一个50kHz的PWM信号,用于测试I2S+ADC极限采样率下的采样情况,这时候波形跳变较为严重,不过还能看出波形以及正确统计出频率。
44 |
45 | 
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 |
--------------------------------------------------------------------------------