回头再解释,先 mark 2020年4月28日。

2020年5月1日 今日有空,稍微更新一下,主要在这里备份一下相关的 code 和详细的问题分析和具体解释。

继着上次的 https://www.cnblogs.com/juwan/p/12389600.html 的内容,上次只定位了问题和简单解决了问题,调整了 Python 的 Code ,现在主要是旧 Code 不稳定,下载的文件容易出问题,容易因为二进制文件产生错误。

那么如何解决呢?

我回顾一下我的这 4 天的开发历程,第一天,基于旧接收框架重新整理 Code ,第二天准备到位开始测试,测试崩盘没有任何改造,镇定思痛,第三天重新上状态机架构,第四天大量测试,缝缝补补后符合预期执行,趋于完美。

先上逻辑分析仪捕获数据观察数据流,注意我压缩了很多写的过程,只交待重点,有理有据的代码让人信服,如果有漏洞也是细节的马虎,思路对了大体是不会有太大问题的。

在 HTTP 下载文件的时候,可以看到它接收数据是以 \r\n+IPD 开头的数据,如 '+IPD,3,1452:' or '+IPD,1452:' 两类,经过测试,确实是符合预期的返回指定长度的数据,但会在最后的结束的时候附带 CLOSED\r\n 数据表示传输结束。

这里我们注意到两个问题,以及旧代码为什么不能抵抗高速传输的异常数据所产生的错误,这在上次的分析就已经提到这个数据流的问题,我们先理清问题。

我们知道了传输的规则和传输的形式,例如 AT 指令 和 传输的数据 是混在同一条通道中的,这意味着二进制数据中一旦出现类似 AT 指令的数据必然导致后续的数据错乱,又或者是传输的缓冲区异常导致程序跑飞等等。

这些错误都在旧代码中体现了,这里不再细讲,直接开始编写新代码适用新的接口。

我们知道这个函数是可重入的,所以一开始在剥离基础框架的时候就要最大化的保留重入的接口,例如,接收数据完成后,将数据保留到一个缓冲区中,在下一次的重入的时候,取走足够数量的数据,如果不够,则继续接收数据,如下代码的设计。

// required data already in buf, just return data

size = Buffer_Size(&nic->buffer);

if (size >= out_buff_len) { // 请求的代码长度小于缓冲区的长度,直接返回数据

Buffer_Gets(&nic->buffer, (uint8_t *)out_buff, out_buff_len);

if (data_len) {

*data_len = out_buff_len;

}

size = size > out_buff_len ? out_buff_len : size;

frame_sum -= size; // 总体的缓冲区数据要对应减少,同时判断是否没有更多的数据了,如果为负数则报错。

// printk("Buffer_Size frame_sum %d size %d *data_len %d\n", frame_sum, size, *data_len);

if (frame_sum <= 0) {

Buffer_Clear(&nic->buffer);

if (*peer_closed) { //buffer empty, return EOF

return -2;

}

}

return out_buff_len;

}

围绕这个思路去,这个函数就形成一种非常适合 FSM 状态机的接口,同时必须要有 buffer 去保留上一次的状态与数据。

为此可以将其构建一种基础状态机模型,二话不说先上架构,保持 IDLE, IPD, DATA, EXIT 四种状态,本来有 CLOSED 状态的,后来想了一下还不到时候就移除了。

enum fsm_state { IDLE, IPD, DATA, EXIT } State = IDLE;

static uint8_t tmp_buf[1560 * 2] = {0}; // tmp_buf as queue 1560

uint32_t tmp_bak = 0, tmp_state = 0, tmp_len = 0, tmp_pos = 0;

mp_uint_t interrupt = 0, uart_recv_len = 0, res = 0;

while (State != EXIT) {

// wait any uart data

uart_recv_len = uart_rx_any(nic->uart_obj);

if (uart_recv_len > 0) {

if (tmp_len >= sizeof(tmp_buf)) { // lock across

// printk("lock across frame_len %d tmp_len %d\n", frame_len, tmp_len);

tmp_len = 0;

}

res = uart_stream->read(nic->uart_obj, tmp_buf + tmp_len, 1, &err);

// printk("%s | tmp_buf %s res %d err %d\n", __func__, tmp_buf, res, err);

if (res == 1 && err == 0) {

interrupt = mp_hal_ticks_ms();

// backup tmp_len to tmp_pos (tmp_pos - 1)

tmp_pos = tmp_len, tmp_len += 1; // buffer push

// printk("[%02X]", tmp_buf[tmp_pos]);

if (State == IDLE) {

continue;

}

if (State == IPD) {

continue;

}

if (State == DATA) {

// printk("%s | frame_len %d tmp_len %d tmp_buf[tmp_pos] %02X\n", __func__, frame_len, tmp_len, tmp_buf[tmp_pos]);

continue;

}

} else {

State = EXIT;

}

}

if (mp_hal_ticks_ms() - interrupt > timeout) {

break; // uart no return data to timeout break

}

if (*peer_closed) {

break; // disconnection

}

}

我们看到这个 FSM 的基础保证是,确保访问边界被控制住,防止程序指针异常访问导致跑飞(在我前一篇有所提及)。

if (tmp_len >= sizeof(tmp_buf)) { // lock across

// printk("lock across frame_len %d tmp_len %d\n", frame_len, tmp_len);

tmp_len = 0;

}

其次是超时机制,确保任何情况下都可以离开状态机。

if (mp_hal_ticks_ms() - interrupt > timeout) {

break; // uart no return data to timeout break

}

这样可以保障程序在任何场合下都不会陷入底层无法离开。

接下来就是当缓冲区有数据、数据接收正常、以及每次读取一字节成功的基础上,进行字符串的匹配和状态转移的分类处理。

// wait any uart data

uart_recv_len = uart_rx_any(nic->uart_obj);

if (uart_recv_len > 0) {

if (tmp_len >= sizeof(tmp_buf)) { // lock across

// printk("lock across frame_len %d tmp_len %d\n", frame_len, tmp_len);

tmp_len = 0;

}

res = uart_stream->read(nic->uart_obj, tmp_buf + tmp_len, 1, &err);

// printk("%s | tmp_buf %s res %d err %d\n", __func__, tmp_buf, res, err);

if (res == 1 && err == 0) {

interrupt = mp_hal_ticks_ms();

// backup tmp_len to tmp_pos (tmp_pos - 1)

tmp_pos = tmp_len, tmp_len += 1; // buffer push

// printk("[%02X]", tmp_buf[tmp_pos]);

if (State == IDLE) {

continue;

}

if (State == IPD) {

continue;

}

if (State == DATA) {

// printk("%s | frame_len %d tmp_len %d tmp_buf[tmp_pos] %02X\n", __func__, frame_len, tmp_len, tmp_buf[tmp_pos]);

continue;

}

} else {

State = EXIT;

}

}

这时候最后一个状态是用来离开的标记。

我们开始脑里模拟执行过程和假设,第一种状态就是空转(IDLE),基本上每个状态机都是从这里开始的。

在 IDLE 期间要做的事情就是清理无关的数据,如下代码,直到符合预期的怀疑对象出现为止。

if (State == IDLE) {

if (tmp_buf[tmp_pos] == '+') {

tmp_state = 1, tmp_bak = tmp_pos, State = IPD;

continue;

} else {

// printk("(%02X)", tmp_buf[tmp_pos]);

tmp_len -= 1; // clear don't need data, such as (0D)(0A)

continue;

}

}

如上代码,在空转状态下,遇到 '+' 出现后,会进行假设性的状态转移,如果不符合预期的数据会跌回 ILDE 状态,然后我们看看 IPD 状态的实现。

if (tmp_pos - tmp_bak > 12) { // Over the length of the '+IPD,3,1452:' or '+IPD,1452:'

tmp_state = 0, State = IDLE;

continue;

}

if (0 < tmp_state && tmp_state < 5) {

// printk("(%d, %02X) [%d, %02X]\n", tmp_pos, tmp_buf[tmp_pos], tmp_pos - tmp_bak, ("+IPD,")[tmp_pos - tmp_bak]);

if (tmp_buf[tmp_pos] == ("+IPD,")[tmp_pos - tmp_bak]) {

tmp_state += 1; // tmp_state 1 + "IPD," to tmp_state 5

} else {

tmp_state = 0, State = IDLE;

}

continue;

}

if (tmp_state == 5 && tmp_buf[tmp_pos] == ':')

{

tmp_state = 6, State = IDLE;

tmp_buf[tmp_pos + 1] = '\0'; // lock tmp_buf

// printk("%s | is `IPD` tmp_bak %d tmp_len %d command %s\n", __func__, tmp_bak, tmp_len, tmp_buf + tmp_bak);

char *index = strstr((char *)tmp_buf + tmp_bak + 5 /* 5 > '+IPD,' and `+IPD,325:` in tmp_buf */, ",");

int ret = 0, len = 0;

if (index) { // '+IPD,3,1452:'

ret = sscanf((char *)tmp_buf + tmp_bak, "+IPD,%hhd,%d:", &mux_id, &len);

if (ret != 2 || mux_id < 0 || mux_id > 4 || len <= 0) {

; // Misjudge or fail, return, or clean up later

} else {

tmp_len = tmp_bak, tmp_bak = 0; // Clean up the commands in the buffer and roll back the data

frame_len = len, State = DATA;

}

} else { // '+IPD,1452:'

ret = sscanf((char *)tmp_buf + tmp_bak, "+IPD,%d:", &len);

if (ret != 1 || len <= 0) {

; // Misjudge or fail, return, or clean up later

} else {

tmp_len = tmp_bak, tmp_bak = 0; // Clean up the commands in the buffer and roll back the data

frame_len = len, State = DATA;

}

}

continue;

}

由于这是第三次重构的代码,所以里面加了很多保护措施,但实际上这些措施不一定会遇到,它会随着各种高频传输调试中触发,代码要能够抵抗这些异常数据。

我们看到 if (tmp_buf[tmp_pos] == ("+IPD,")[tmp_pos - tmp_bak]) { 的循环表示的就是 strstr 的匹配,但这里的匹配我稍微偷懒了一下简写了代码。

if (0 < tmp_state && tmp_state < 5) {

// printk("(%d, %02X) [%d, %02X]\n", tmp_pos, tmp_buf[tmp_pos], tmp_pos - tmp_bak, ("+IPD,")[tmp_pos - tmp_bak]);

if (tmp_buf[tmp_pos] == ("+IPD,")[tmp_pos - tmp_bak]) {

tmp_state += 1; // tmp_state 1 + "IPD," to tmp_state 5

} else {

tmp_state = 0, State = IDLE;

}

continue;

}

因为整个环境没有基础容器的支持,例如队列,所以我只能通过代码中的索引去模拟队列执行,保障整个执行的有序状态,当匹配完成后,会从 IPD 进入 DATA 态,此时意味着之后的数据都被视为数据,长度以 IPD 指示的为准。

tmp_len = tmp_bak, tmp_bak = 0; // Clean up the commands in the buffer and roll back the data

frame_len = len, State = DATA;

进入 DATA 态的时候就要思考终止条件的状态了,有两种可能,一种是标识的长度数据走完了,这表示应该要转移状态到 ILDE 了,但期望从这个状态中发现终止条件。

if (frame_len < 0) {

if (frame_len == -1 && tmp_buf[tmp_pos] == 'C') { // wait "CLOSED\r\n"

frame_bak = frame_len;

continue;

}

if (tmp_state == 6 && tmp_buf[tmp_pos] == '\r') {

tmp_state = 7;

continue;

}

if (tmp_state == 7 && tmp_buf[tmp_pos] == '\n') {

if (frame_len == -2) { // match +IPD EOF (\r\n)

tmp_state = 0, State = IDLE;

// After receive complete, confirm the data is enough

size = Buffer_Size(&nic->buffer);

// printk("%s | size %d out_buff_len %d\n", __func__, size, out_buff_len);

if (size >= out_buff_len) { // data enough

// printk("%s | recv out_buff_len overflow\n", __func__);

State = EXIT;

}

} else if (frame_len == -8 && frame_len == -1) {

// Get "CLOSED\r\n"

peer_just_closed = *peer_closed = true;

frame_bak = 0, tmp_state = 0, State = EXIT;

} else {

tmp_state = 6;

}

continue;

}

// 存在异常,没有得到 \r\n 的匹配,并排除 CLOSED\r\n 的指令触发的可能性,意味着传输可能越界出错了 \r\n ,则立即回到空闲状态。

if (frame_len <= -1 && frame_bak != -1) {

// printk("%s | tmp_state %d frame_len %d tmp %02X\n", __func__, tmp_state, frame_len, tmp_buf[tmp_pos]);

State = IDLE;

continue;

}

}

如我所假设的两种状态 \r\n 和 CLOSED\r\n 这两类,但还有最终保障措施,假设无法符合我的预期的结尾,要尽快回归 ILDE 重新进入匹配,否则数据将一片混乱,这个只能保障 1 、 2 字节的误差,对于缓冲区溢出复写的情况无法抵抗。

到这里我们还要注意到很多地方可能会出错和溢出,比如有如下考虑:

上层 Python 接收数据的变量溢出,无法继续 read 更多,积压数据未处理。

原始串口缓冲区溢出后,循环缓冲复写。

内部状态机解析的数据缓冲长度(已经被保护了,但出错了会导致其中的一帧丢失)

状态机解析后的数据缓冲变量写入可能会失败,夹在 C 与 Python 之间交互的一层缓冲区,主要用来供应 socket 层的数据。

因此这里面的任何一个环境出错,都可能导致 HTTP 的原始下载数据出错,但所辛都可以完整下好不会出现指令与二进制数据混淆的情况了。

基于这个接收框架,还可以进一步的拓展出 多 socket 的通信 和 其他 AT 指令的通信接口,匹配状态和解析后分类到各自对应的容器即可。

旧接口函数抵抗异常的设计不够多,所以之后测试完整了后,可以基于新的接口继续发展新功能。

我的测试结果如下:

在串口波特率 921600(1M)约 92 kb/s 的下载速度下载 384kb kmodel 模型文件的过程。

若是不写入 SD 卡的基础上,完美接收完毕,耗时 8s 。

若是写入我的 256M 的路边野卡,完整下载时 10s,无任何损坏,但受到网络波动则会出现下载的文件中会存在失败的帧,下载过程的好坏参半。

在串口波特率 576000(5M)约 57 kb/s 的下载速度下载 5M kmodel 模型文件的过程。

写入SD卡的过程,下载 5M 文件无错且完整,较为稳定,少有失败的情况。

从这个测试的结果来看,将 SD 卡写入数据是一个影响很大的变数,要么将其读写分离加缓冲在其中,减少 SD 卡带来的负面影响,优先把 HTTP 的数据带回内存之中。

这和串口缓冲区有关,如现在假定的 10k 字节,而 921600 (92kb/s),意味着 read 数据要在 10k/x:100kb/s, x = 0.1s 内取走全部数据,否则 IO 不匹配,缓冲区会溢出,之后的数据都会被放弃从而形成接收数据的断层。

python 的 code 执行模型如下

while True:

tmp = wifi.read()

file.write(tmp)

所以再下一次的 wifi.read() 因符合下述读写分离模型,这只是伪代码。不用太计较。

# thread 0

while True:

tmp = wifi.read()

sleep(0.1)

# thread 1

while True:

file.write(tmp)

sleep(x)

这次我们必须在硬件的内存和 SD 卡的写入数据中做一个选择,至少保证 SD 写入速度应大于 100kb/s 从而保持 IO 的速率基本一致,减少下载文件的异常。

其他可能存在的问题,就比如有时候发送数据会请求失败,还不知道是哪个死角没考虑到,也许之后就会有人发现了吧?

之后的内容都是代码的备份,分为 新接口函数,旧接口函数,对应的 Python 测试代码,有兴趣的可以自己调试试试。

/*----------------------------------------------------------------------------*/

/* +IPD,,: */

/* +IPD,: */

/**

*

* @return -1: parameters error, -2: EOF, -3: timeout, -4:peer closed and no data in buffer

*/

uint32_t recvPkg(esp8285_obj *nic, char *out_buff, uint32_t out_buff_len, uint32_t *data_len, uint32_t timeout, char *coming_mux_id, bool *peer_closed, bool first_time_recv)

{

// printk("%s | head obl %d *dl %d tu %d ftr %d \n", __func__, out_buff_len, *data_len, timeout, first_time_recv);

int err = 0, size = 0;

bool peer_just_closed = false;

// only for single socket but need socket map & buffer TODO:

const mp_stream_p_t *uart_stream = mp_get_stream(nic->uart_obj);

static int8_t mux_id = -1;

static int32_t frame_sum = 0, frame_len = 0, frame_bak = 0;

// parameters check

if (out_buff == NULL) {

return -1;

}

if (first_time_recv) {

frame_sum = frame_len = 0;

}

// required data already in buf, just return data

size = Buffer_Size(&nic->buffer);

if (size >= out_buff_len) {

Buffer_Gets(&nic->buffer, (uint8_t *)out_buff, out_buff_len);

if (data_len) {

*data_len = out_buff_len;

}

size = size > out_buff_len ? out_buff_len : size;

frame_sum -= size;

// printk("Buffer_Size frame_sum %d size %d *data_len %d\n", frame_sum, size, *data_len);

if (frame_sum <= 0) {

Buffer_Clear(&nic->buffer);

if (*peer_closed) { //buffer empty, return EOF

return -2;

}

}

return out_buff_len;

}

enum fsm_state { IDLE, IPD, DATA, EXIT } State = IDLE;

static uint8_t tmp_buf[1560 * 2] = {0}; // tmp_buf as queue 1560

uint32_t tmp_bak = 0, tmp_state = 0, tmp_len = 0, tmp_pos = 0;

mp_uint_t interrupt = 0, uart_recv_len = 0, res = 0;

while (State != EXIT) {

// wait any uart data

uart_recv_len = uart_rx_any(nic->uart_obj);

if (uart_recv_len > 0) {

if (tmp_len >= sizeof(tmp_buf)) { // lock across

// printk("lock across frame_len %d tmp_len %d\n", frame_len, tmp_len);

tmp_len = 0;

}

res = uart_stream->read(nic->uart_obj, tmp_buf + tmp_len, 1, &err);

// printk("%s | tmp_buf %s res %d err %d\n", __func__, tmp_buf, res, err);

if (res == 1 && err == 0) {

interrupt = mp_hal_ticks_ms();

// backup tmp_len to tmp_pos (tmp_pos - 1)

tmp_pos = tmp_len, tmp_len += 1; // buffer push

// printk("[%02X]", tmp_buf[tmp_pos]);

if (State == IDLE) {

if (tmp_buf[tmp_pos] == '+') {

tmp_state = 1, tmp_bak = tmp_pos, State = IPD;

continue;

} else {

// printk("(%02X)", tmp_buf[tmp_pos]);

tmp_len -= 1; // clear don't need data, such as (0D)(0A)

continue;

}

}

if (State == IPD) {

if (tmp_pos - tmp_bak > 12) { // Over the length of the '+IPD,3,1452:' or '+IPD,1452:'

tmp_state = 0, State = IDLE;

continue;

}

if (0 < tmp_state && tmp_state < 5) {

// printk("(%d, %02X) [%d, %02X]\n", tmp_pos, tmp_buf[tmp_pos], tmp_pos - tmp_bak, ("+IPD,")[tmp_pos - tmp_bak]);

if (tmp_buf[tmp_pos] == ("+IPD,")[tmp_pos - tmp_bak]) {

tmp_state += 1; // tmp_state 1 + "IPD," to tmp_state 5

} else {

tmp_state = 0, State = IDLE;

}

continue;

}

if (tmp_state == 5 && tmp_buf[tmp_pos] == ':')

{

tmp_state = 6, State = IDLE;

tmp_buf[tmp_pos + 1] = '\0'; // lock tmp_buf

// printk("%s | is `IPD` tmp_bak %d tmp_len %d command %s\n", __func__, tmp_bak, tmp_len, tmp_buf + tmp_bak);

char *index = strstr((char *)tmp_buf + tmp_bak + 5 /* 5 > '+IPD,' and `+IPD,325:` in tmp_buf */, ",");

int ret = 0, len = 0;

if (index) { // '+IPD,3,1452:'

ret = sscanf((char *)tmp_buf + tmp_bak, "+IPD,%hhd,%d:", &mux_id, &len);

if (ret != 2 || mux_id < 0 || mux_id > 4 || len <= 0) {

; // Misjudge or fail, return, or clean up later

} else {

tmp_len = tmp_bak, tmp_bak = 0; // Clean up the commands in the buffer and roll back the data

frame_len = len, State = DATA;

}

} else { // '+IPD,1452:'

ret = sscanf((char *)tmp_buf + tmp_bak, "+IPD,%d:", &len);

if (ret != 1 || len <= 0) {

; // Misjudge or fail, return, or clean up later

} else {

tmp_len = tmp_bak, tmp_bak = 0; // Clean up the commands in the buffer and roll back the data

frame_len = len, State = DATA;

}

}

continue;

}

}

if (State == DATA) {

// printk("%s | frame_len %d tmp_len %d tmp_buf[tmp_pos] %02X\n", __func__, frame_len, tmp_len, tmp_buf[tmp_pos]);

frame_len -= tmp_len, tmp_len = 0; // get data

if (frame_len < 0) {

if (frame_len == -1 && tmp_buf[tmp_pos] == 'C') { // wait "CLOSED\r\n"

frame_bak = frame_len;

continue;

}

if (tmp_state == 6 && tmp_buf[tmp_pos] == '\r') {

tmp_state = 7;

continue;

}

if (tmp_state == 7 && tmp_buf[tmp_pos] == '\n') {

if (frame_len == -2) { // match +IPD EOF (\r\n)

tmp_state = 0, State = IDLE;

// After receive complete, confirm the data is enough

size = Buffer_Size(&nic->buffer);

// printk("%s | size %d out_buff_len %d\n", __func__, size, out_buff_len);

if (size >= out_buff_len) { // data enough

// printk("%s | recv out_buff_len overflow\n", __func__);

State = EXIT;

}

} else if (frame_len == -8 && frame_len == -1) {

// Get "CLOSED\r\n"

peer_just_closed = *peer_closed = true;

frame_bak = 0, tmp_state = 0, State = EXIT;

} else {

tmp_state = 6;

}

continue;

}

// 存在异常,没有得到 \r\n 的匹配,并排除 CLOSED\r\n 的指令触发的可能性,意味着传输可能越界出错了 \r\n ,则立即回到空闲状态。

if (frame_len <= -1 && frame_bak != -1) {

// printk("%s | tmp_state %d frame_len %d tmp %02X\n", __func__, tmp_state, frame_len, tmp_buf[tmp_pos]);

State = IDLE;

continue;

}

} else {

// for(int i = 0; i < tmp_len; i++) {

// int tmp = tmp_buf[i];

// printk("[%02X]", tmp);

// }

// printk("%s | frame_len %d tmp_len %d\n", __func__, frame_len, tmp_pos + 1);

// printk("%.*s", tmp_len, tmp_buf);

if (!Buffer_Puts(&nic->buffer, tmp_buf, (tmp_pos + 1))) {

printk("%s | network->buffer overflow Buffer Max %d Size %d\n", __func__, ESP8285_BUF_SIZE, Buffer_Size(&nic->buffer));

State = EXIT;

} else {

// reduce data len

// printk("[%02X]", tmp_buf[(tmp_pos + 1)]);

frame_sum += (tmp_pos + 1);

}

// printk("frame_sum %d frame_len %d tmp_len %d\n", frame_sum, frame_len, tmp_pos + 1);

}

continue;

}

} else {

State = EXIT;

}

}

if (mp_hal_ticks_ms() - interrupt > timeout) {

break; // uart no return data to timeout break

}

if (*peer_closed) {

break; // disconnection

}

}

size = Buffer_Size(&nic->buffer);

// peer closed and no data in buffer

if (size == 0 && !peer_just_closed && *peer_closed) {

frame_sum = 0;

return -4;

}

size = size > out_buff_len ? out_buff_len : size;

Buffer_Gets(&nic->buffer, (uint8_t *)out_buff, size);

if (data_len) {

*data_len = size;

}

frame_sum -= size;

if (frame_sum <= 0 || peer_just_closed) {

Buffer_Clear(&nic->buffer);

if (peer_just_closed)

{

frame_sum = 0;

return -2;

}

}

// printk(" %s | tail obl %d *dl %d se %d\n", __func__, out_buff_len, *data_len, size);

return size;

}

肝了三天了,彻底优化了 AT 8285 的通信过程,等测试得差不多了再提交,目前只测到了波特率 576000 下 HTTP 下载 K-Flash.zip (5M)二进制文件,数据完整,接收过程大体是没有太大问题了。

备份旧 CODE 。

uint32_t bak_recvPkg(esp8285_obj *nic, char *out_buff, uint32_t out_buff_len, uint32_t *data_len, uint32_t timeout, char *coming_mux_id, bool *peer_closed, bool first_time_recv)

{

const mp_stream_p_t *uart_stream = mp_get_stream(nic->uart_obj);

static uint8_t temp_buff1[2048] = {0};

static uint8_t temp_buff2[2048] = {0};

uint16_t temp_buff1_len = 0;

uint16_t temp_buff2_len = 0;

uint8_t find_frame_flag_index = 0;

static int8_t mux_id = -1;

static int16_t frame_len = 0;

static int32_t frame_len_sum = 0; //only for single socket TODO:

// bool overflow = false;

int ret = 0;

int size = 0;

int errcode;

mp_uint_t start1 = 0, start2 = 0;

bool no_frame_flag = false;

bool new_frame = false;

mp_uint_t data_len_in_uart_buff = 0;

bool peer_just_closed = false;

// parameters check

if (out_buff == NULL)

{

return -1;

}

// // printk("\r if (out_buff == NULL) { \n");

// init vars

memset(temp_buff1, 0, sizeof(temp_buff1));

memset(temp_buff2, 0, sizeof(temp_buff2));

if (first_time_recv)

{

frame_len = 0;

frame_len_sum = 0;

}

// required data already in buf, just return data

uint32_t buff_size = Buffer_Size(&nic->buffer);

// // printk("\r if(buff_size >= out_buff_len) \n");

// printk("\r buff_size %d out_buff_len %d\n", buff_size, out_buff_len);

if (buff_size >= out_buff_len)

{

Buffer_Gets(&nic->buffer, (uint8_t *)out_buff, out_buff_len);

if (data_len)

*data_len = out_buff_len;

frame_len_sum -= size;

if (frame_len_sum <= 0)

{

frame_len = 0;

frame_len_sum = 0;

Buffer_Clear(&nic->buffer);

if (*peer_closed) //buffer empty, return EOF

{

return -2;

}

}

return out_buff_len;

}

// read from uart buffer, if not frame start flag, put into nic buffer

// and need wait for full frame flag in 200ms(can be fewer), frame format: '+IPD,id,len:data' or '+IPD,len:data'

// wait data from uart buffer if not timeout

start2 = mp_hal_ticks_ms();

data_len_in_uart_buff = uart_rx_any(nic->uart_obj);

// // printk("\r uart_stream->read %p \n", uart_stream->read);

do

{

if (data_len_in_uart_buff > 0)

{

uart_stream->read(nic->uart_obj, temp_buff1 + temp_buff1_len, 1, &errcode);

if (find_frame_flag_index == 0 && temp_buff1[temp_buff1_len] == '+')

{

++find_frame_flag_index;

start1 = mp_hal_ticks_ms();

}

else if (find_frame_flag_index == 1 && temp_buff1[temp_buff1_len] == 'I')

{

++find_frame_flag_index;

}

else if (find_frame_flag_index == 2 && temp_buff1[temp_buff1_len] == 'P')

{

++find_frame_flag_index;

}

else if (find_frame_flag_index == 3 && temp_buff1[temp_buff1_len] == 'D')

{

++find_frame_flag_index;

}

else if (find_frame_flag_index == 4 && temp_buff1[temp_buff1_len] == ',')

{

++find_frame_flag_index;

}

else if (find_frame_flag_index == 5)

{

if (temp_buff1[temp_buff1_len] == ':')

{ // '+IPD,3,1452:' or '+IPD,1452:'

temp_buff1[temp_buff1_len + 1] = '\0';

char *index = strstr((char *)temp_buff1 + 5, ",");

if (index)

{ // '+IPD,3,1452:'

ret = sscanf((char *)temp_buff1, "+IPD,%hhd,%hd:", &mux_id, &frame_len);

if (ret != 2 || mux_id < 0 || mux_id > 4 || frame_len <= 0)

{ // format not satisfy, it's data

no_frame_flag = true;

}

else

{ // find frame start flag, although it may also data

new_frame = true;

}

}

else

{ // '+IPD,1452:'

ret = sscanf((char *)temp_buff1, "+IPD,%hd:", &frame_len);

if (ret != 1 || frame_len <= 0)

{ // format not satisfy, it's data

no_frame_flag = true;

}

else

{ // find frame start flag, although it may also data

new_frame = true;

// // printk("new frame:%d\r\n", frame_len);

}

}

}

}

else

{ // not match frame start flag, put into nic buffer

no_frame_flag = true;

}

// new frame or data

// or wait for frame start flag timeout(300ms, can be fewer), maybe they're data

if (new_frame || no_frame_flag || temp_buff1_len >= 12 ||

(find_frame_flag_index && (mp_hal_ticks_ms() - start1 > 300))) // '+IPD,3,1452:'

{

if (!new_frame)

{

if (frame_len_sum > 0)

{

// // printk("if(frame_len_sum > 0) { temp_buff2_len-%d temp_buff1_len-%d\r\n", temp_buff2_len, temp_buff1_len);

if (!Buffer_Puts(&nic->buffer, temp_buff1, temp_buff1_len + 1))

{

// printk("data_len_in_uart_buff %u temp_buff1_len %u temp_buff1 ", data_len_in_uart_buff, temp_buff1_len);

for (int i = 0; i < temp_buff1_len; i++) {

int tmp = temp_buff1[i];

// printk("(%c %02X)", temp_buff1[i], tmp);

}

// printk("\n");

// printk("data_len_in_uart_buff %u temp_buff1_len %u temp_buff1 ", data_len_in_uart_buff, temp_buff1_len);

for (int i = 0; i < temp_buff1_len; i++) {

int tmp = temp_buff1[i];

// printk("(%c %02X)", temp_buff1[i], tmp);

}

// printk("\n");

// overflow = true;

// break;//TODO:

}

}

else

{

if (temp_buff1[0] == 'C')

{

memset(temp_buff2, 0, sizeof(temp_buff2));

}

// // printk("-%d:%s\r\n", temp_buff2_len, temp_buff2);

// // printk("!(if(frame_len_sum > 0) {) temp_buff2_len-%d temp_buff1_len-%d\r\n", temp_buff2_len, temp_buff1_len);

temp_buff2[temp_buff2_len++] = temp_buff1[0];

// // printk("%c", temp_buff1[0]); //TODO: optimize uart overflow, if uart overflow, uncomment this will print some data

// // printk("-%d:%s\r\n", temp_buff2_len, temp_buff2);

if (strstr((const char *)temp_buff2, "CLOSED\r\n") != NULL)

{

// // printk("pear closed\r\n");

*peer_closed = true;

peer_just_closed = true;

break;

}

}

}

else

{

frame_len_sum += frame_len;

}

find_frame_flag_index = 0;

temp_buff1_len = 0;

new_frame = false;

no_frame_flag = false;

// enough data as required

size = Buffer_Size(&nic->buffer);

if (size >= out_buff_len) // data enough

break;

if (frame_len_sum != 0 && frame_len_sum <= size) // read at least one frame ok

{

break;

}

continue;

}

++temp_buff1_len;

}

if (timeout != 0 && (mp_hal_ticks_ms() - start2 > timeout) && !find_frame_flag_index)

{

// // printk("\n timeout %d start2 %d (mp_hal_ticks_ms() - start2 > timeout) %d find_frame_flag_index %d \n", timeout, start2, (mp_hal_ticks_ms() - start2 > timeout), find_frame_flag_index);

// // printk("\r-3 recvPkg nic %p out_buff %p out_buff_len %p data_len %p timeout %p coming_mux_id %p peer_closed %p first_time_recv %p\n", nic, out_buff, &out_buff_len, data_len, &timeout, coming_mux_id, peer_closed, &first_time_recv);

// printk("data_len_in_uart_buff %u temp_buff1_len %u temp_buff1 ", data_len_in_uart_buff, temp_buff1_len);

for (int i = 0; i < temp_buff1_len; i++) {

int tmp = temp_buff1[i];

// printk("(%c %02X)", temp_buff1[i], tmp);

}

// printk("\n");

return -3;

}

data_len_in_uart_buff = uart_rx_any(nic->uart_obj);

// // printk("\n2-%hd 1-%hd\n", temp_buff2_len, temp_buff1_len);

} while ((timeout || find_frame_flag_index) && (!*peer_closed || data_len_in_uart_buff > 0));

size = Buffer_Size(&nic->buffer);

if (size == 0 && !peer_just_closed && *peer_closed) //peer closed and no data in buffer

{

frame_len = 0;

frame_len_sum = 0;

// // printk("\r-4 recvPkg nic %p out_buff %p out_buff_len %p data_len %p timeout %p coming_mux_id %p peer_closed %p first_time_recv %p\n", nic, out_buff, &out_buff_len, data_len, &timeout, coming_mux_id, peer_closed, &first_time_recv);

return -4;

}

size = size > out_buff_len ? out_buff_len : size;

Buffer_Gets(&nic->buffer, (uint8_t *)out_buff, size);

if (data_len)

*data_len = size;

frame_len_sum -= size;

if (frame_len_sum <= 0 || peer_just_closed)

{

frame_len = 0;

frame_len_sum = 0;

Buffer_Clear(&nic->buffer);

if (peer_just_closed)

{

// // printk("\r-2 recvPkg nic %p out_buff %p out_buff_len %p data_len %p timeout %p coming_mux_id %p peer_closed %p first_time_recv %p\n", nic, out_buff, &out_buff_len, data_len, &timeout, coming_mux_id, peer_closed, &first_time_recv);

return -2;

}

}

// mp_printf(&mp_plat_print, "[MaixPy] %s | size %d out_buff_len %d *data_len %d \n", __func__, size, out_buff_len, *data_len);

return size;

}

旧 code 最大的问题在于处理过程单向思维,新 code 主要是上状态机分离 AT 指令和数据的处理过程。

对应的 HTTP Download 的 Python Code 也备份一下。

"""

The MIT License (MIT)

Copyright © 2018 Jean-Christophe Bos & HC² (www.hc2.fr)

"""

from struct import pack

import socket

import gc

class MicroWebCli:

# ============================================================================

# ===( Class AuthBasic )=====================================================

# ============================================================================

class AuthBasic:

# ------------------------------------------------------------------------

def __init__(self, user, password):

if password is None:

password = ''

if not 'b2a_base64' in globals():

from binascii import b2a_base64

cred = '%s:%s' % (user, password)

self._auth = 'Basic %s' % b2a_base64(

cred.encode()).decode().strip()

# ------------------------------------------------------------------------

def Apply(self, microWebCli):

microWebCli.Headers['Authorization'] = self._auth

# ============================================================================

# ===( Class AuthToken )=====================================================

# ============================================================================

class AuthToken:

# ------------------------------------------------------------------------

def __init__(self, token):

self._auth = 'Bearer %s' % token

# ------------------------------------------------------------------------

def Apply(self, microWebCli):

microWebCli.Headers['Authorization'] = self._auth

# ============================================================================

# ===( Utils )===============================================================

# ============================================================================

def _tryAllocByteArray(size):

for x in range(10):

try:

gc.collect()

return bytearray(size)

except:

pass

return None

# ----------------------------------------------------------------------------

@staticmethod

def _quote(s, safe='/'):

r = ''

for c in str(s):

if (c >= 'a' and c <= 'z') or \

(c >= '0' and c <= '9') or \

(c >= 'A' and c <= 'Z') or \

(c in '.-_') or (c in safe) :

r += c

else:

for b in c.encode('UTF-8'):

r += '%%%02X' % b

return r

# ----------------------------------------------------------------------------

@staticmethod

def _urlEncode(s):

return MicroWebCli._quote(s, ';/?:@&=+$,')

# ----------------------------------------------------------------------------

@staticmethod

def _unquote(s):

r = str(s).split('%')

try:

b = r[0].encode()

for i in range(1, len(r)):

try:

b += bytes([int(r[i][:2], 16)]) + r[i][2:].encode()

except:

b += b'%' + r[i].encode()

return b.decode('UTF-8')

except:

return str(s)

# ----------------------------------------------------------------------------

@staticmethod

def _unquote_plus(s):

return MicroWebCli._unquote(str(s).replace('+', ' '))

# ----------------------------------------------------------------------------

def GETRequest(url,

queryParams=None,

auth=None,

connTimeoutSec=10,

socks5Addr=None):

c = MicroWebCli(url,

auth=auth,

connTimeoutSec=connTimeoutSec,

socks5Addr=socks5Addr)

if queryParams:

c.QueryParams = queryParams

c.OpenRequest()

r = c.GetResponse()

if r.IsSuccess():

return r.ReadContent()

r.Close()

if r.IsLocationMoved():

return MicroWebCli.GETRequest(r.LocationMovedURL(), queryParams,

auth, connTimeoutSec, socks5Addr)

return None

# ----------------------------------------------------------------------------

def POSTRequest(url,

formData={},

auth=None,

connTimeoutSec=10,

socks5Addr=None):

c = MicroWebCli(url,

method='POST',

auth=auth,

connTimeoutSec=connTimeoutSec,

socks5Addr=socks5Addr)

c.OpenRequestFormData(formData)

r = c.GetResponse()

if r.IsSuccess():

return r.ReadContent()

r.Close()

if r.IsLocationMoved():

return MicroWebCli.POSTRequest(r.LocationMovedURL(), formData,

auth, connTimeoutSec, socks5Addr)

return None

# ----------------------------------------------------------------------------

def JSONRequest(url,

o=None,

auth=None,

connTimeoutSec=10,

socks5Addr=None):

c = MicroWebCli(url,

method=('POST' if o else 'GET'),

auth=auth,

connTimeoutSec=connTimeoutSec,

socks5Addr=socks5Addr)

if o:

c.OpenRequestJSONData(o)

else:

c.OpenRequest()

r = c.GetResponse()

if r.IsSuccess():

return r.ReadContentAsJSON()

r.Close()

if r.IsLocationMoved():

return MicroWebCli.JSONRequest(r.LocationMovedURL(), o, auth,

connTimeoutSec, socks5Addr)

return None

# ----------------------------------------------------------------------------

def FileRequest(url,

filepath,

progressCallback=None,

auth=None,

connTimeoutSec=5,

socks5Addr=None):

c = MicroWebCli(url,

auth=auth,

connTimeoutSec=connTimeoutSec,

socks5Addr=socks5Addr)

c.OpenRequest()

r = c.GetResponse()

if r.IsSuccess():

r.WriteContentToFile(filepath, progressCallback)

return r.GetContentType()

r.Close()

if r.IsLocationMoved():

return MicroWebCli.FileRequest(r.LocationMovedURL(), filepath,

progressCallback, auth,

connTimeoutSec, socks5Addr)

return None

# ============================================================================

# ===( Constructor )==========================================================

# ============================================================================

def __init__(self,

url='',

method='GET',

auth=None,

connTimeoutSec=10,

socks5Addr=None):

self.URL = url

self.Method = method

self.Auth = auth

self.ConnTimeoutSec = connTimeoutSec

self._socks5Addr = socks5Addr

self._headers = {}

self._socket = None

self._socketAddr = None

self._response = None

# ============================================================================

# ===( Functions )============================================================

# ============================================================================

def _write(self, data):

try:

data = memoryview(data)

while data:

n = self._socket.write(data)

data = data[n:]

return True

except:

self.Close()

raise Exception('Error to send data on connection')

# ------------------------------------------------------------------------

def _writeFirstLine(self):

path = MicroWebCli._quote(self.Path)

qs = self.QueryString

if qs != '':

path = path + '?' + qs

self._write('%s %s HTTP/1.0\r\n' % (self.Method, path))

# ------------------------------------------------------------------------

def _writeHeader(self, name, value):

self._write("%s: %s\r\n" % (name, value))

# ------------------------------------------------------------------------

def _writeEndHeader(self):

self._write("\r\n")

# ------------------------------------------------------------------------

def OpenRequest(self, data=None, contentType=None, contentLength=None):

if self._socket:

raise Exception('Request is already opened')

if not self.URL:

raise Exception('No URL defined')

if self.Socks5Addr:

if not isinstance(self.Socks5Addr,

tuple) or len(self.Socks5Addr) != 2:

raise Exception('"Socks5Addr" must be a tuple of (host, port)')

host, port = self.Socks5Addr

if not isinstance(host, str) or not isinstance(port, int):

raise Exception('"Socks5Addr" is incorrect ("%s", %s)' %

self.Socks5Addr)

else:

host = self.Host

port = self.Port

self._response = None

try:

err = 0

while 1:

try:

self._socketAddr = socket.getaddrinfo(host, port)[0][-1]

break

except Exception:

err += 1

if err > 5:

raise Exception("get ip failed!")

#cli = socket.socket()

cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

cli.settimeout(self.ConnTimeoutSec)

cli.connect(self._socketAddr)

except:

raise Exception('Error to connect to %s:%s' % (host, port))

if self.Socks5Addr:

err = None

try:

cli.send(b'\x05\x01\x00')

b = cli.read(2)

if b is None or len(b) < 2 or b[0] != 0x05 or b[1] != 0x00:

err = "%s:%s doesn't supports MicroWebCli SOCKS5 client protocol" % self.Socks5Addr

else:

h = self.Host.encode()

p = pack('>H', self.Port)

cli.send(b'\x05\x01\x00\x03' + bytes([len(h)]) + h + p)

b = cli.read(4)

if b is None or len(b) < 4 or b[1] != 0x00:

err = "Error to connect to %s:%s through SOCKS5 server" % (

self.Host, self.Port)

else:

if b[3] == 0x01:

l = 4

elif b[3] == 0x03:

l = cli.read(1)[0]

elif b[3] == 0x04:

l = 16

cli.read(l + 2)

except Exception as ex:

err = 'Error during negotiation with SOCKS5 server (%s)' % ex

if err:

cli.close()

raise Exception(err)

if self.Proto == 'https':

if not 'ssl' in globals():

import ssl

try:

try:

cli = ssl.wrap_socket(cli, timeout=self.ConnTimeoutSec)

except TypeError:

cli = ssl.wrap_socket(cli)

except Exception as ex:

cli.close()

raise Exception(

'Error to open a secure SSL/TLS connection (%s)' % ex)

self._socket = cli

self._writeFirstLine()

if data:

contentLength = len(data)

self._headers['Host'] = self.Host

if self._auth:

try:

self._auth.Apply(self)

except:

raise Exception('Error to apply authentication using %s' %

type(self._auth))

else:

self._headers.pop('Authorization', None)

if contentType:

self._headers['Content-Type'] = contentType

else:

self._headers.pop('Content-Type', None)

if contentLength:

self._headers['Content-Length'] = contentLength

else:

self._headers.pop('Content-Length', None)

self._headers['User-Agent'] = 'MicroWebCli by JC`zic'

for h in self._headers:

self._writeHeader(h, self._headers[h])

self._writeEndHeader()

if data:

self._write(data)

# ------------------------------------------------------------------------

def OpenRequestFormData(self, formData={}):

data = ''

if len(formData) > 0:

for param in formData:

if param != '':

if data != '':

data += '&'

data += MicroWebCli._quote(

param) + '=' + MicroWebCli._quote(formData[param])

self.OpenRequest(data=data,

contentType='application/x-www-form-urlencoded')

# ------------------------------------------------------------------------

def OpenRequestJSONData(self, o=None):

if not 'json' in globals():

import json

try:

data = json.dumps(o)

except:

raise Exception('Error to convert object to JSON format')

self.OpenRequest(data=data, contentType='application/json')

# ------------------------------------------------------------------------

def RequestWriteData(self, data):

self._write(data)

# ------------------------------------------------------------------------

def GetResponse(self):

if not self._response:

self._response = MicroWebCli._response(self, self._socket,

self._socketAddr)

return self._response

# ------------------------------------------------------------------------

def IsClosed(self):

return self._socket is None

# ------------------------------------------------------------------------

def Close(self):

if self._socket:

try:

self._socket.close()

except:

pass

self._socket = None

# ============================================================================

# ===( Properties )===========================================================

# ============================================================================

@property

def ConnTimeoutSec(self):

return self._connTimeoutSec

@ConnTimeoutSec.setter

def ConnTimeoutSec(self, value):

self._connTimeoutSec = int(value) if value and int(value) > 0 else None

# ------------------------------------------------------------------------

@property

def Method(self):

return self._method

@Method.setter

def Method(self, value):

self._method = str(value).upper()

# ------------------------------------------------------------------------

@property

def URL(self):

host = self.Host

if host != '':

proto = self.Proto

port = self.Port

if ( proto == 'http' and port == 80 ) or \

( proto == 'https' and port == 443 ) :

port = ''

else:

port = ':' + str(port)

url = proto + '://' + host + port + self.Path

url = MicroWebCli._urlEncode(url)

qs = self.QueryString

if qs != '':

return url + '?' + qs

return url

return None

@URL.setter

def URL(self, value):

try:

s = str(value)

if '://' in s:

proto, s = s.split('://', 1)

else:

proto = 'http'

except:

raise ValueError('URL error (%s)' % value)

self.Proto = proto

if '/' in s:

host, path = s.split('/', 1)

elif '?' in s:

host, path = s.split('?', 1)

path = '?' + path

else:

host = s

path = ''

if ':' in host:

try:

host, port = host.split(':')

self.Port = port

except:

raise ValueError('URL host:port error (%s)' % host)

self.Host = host

self._queryParams = {}

self.Path = path

# ------------------------------------------------------------------------

@property

def Proto(self):

return self._proto

@Proto.setter

def Proto(self, value):

value = str(value).lower()

if value == 'http':

self._port = 80

elif value == 'https':

self._port = 443

else:

raise ValueError('Unsupported URL protocol (%s)' % value)

self._proto = value

# ------------------------------------------------------------------------

@property

def Host(self):

return self._host

@Host.setter

def Host(self, value):

self._host = MicroWebCli._unquote_plus(str(value))

# ------------------------------------------------------------------------

@property

def Port(self):

return self._port

@Port.setter

def Port(self, value):

self._port = int(value)

# ------------------------------------------------------------------------

@property

def Path(self):

return self._path

@Path.setter

def Path(self, value):

x = value.split('?', 1)

if len(x[0]) > 0:

if x[0][0] != '/':

x[0] = '/' + x[0]

self._path = MicroWebCli._unquote_plus(x[0])

else:

self._path = '/'

if len(x) == 2:

self.QueryString = x[1]

# ------------------------------------------------------------------------

@property

def QueryString(self):

r = ''

for param in self._queryParams:

if param != '':

if r != '':

r += '&'

r += MicroWebCli._quote(param) + '=' + MicroWebCli._quote(

self._queryParams[param])

return r

@QueryString.setter

def QueryString(self, value):

self._queryParams = {}

for x in value.split('&'):

param = x.split('=', 1)

if param[0] != '':

value = MicroWebCli._unquote(

param[1]) if len(param) > 1 else ''

self._queryParams[MicroWebCli._unquote(param[0])] = value

# ------------------------------------------------------------------------

@property

def QueryParams(self):

return self._queryParams

@QueryParams.setter

def QueryParams(self, value):

if not isinstance(value, dict):

raise ValueError('QueryParams must be a dict')

self._queryParams = value

# ------------------------------------------------------------------------

@property

def Headers(self):

return self._headers

@Headers.setter

def Headers(self, value):

if not isinstance(value, dict):

raise ValueError('Headers must be a dict')

self._headers = value

# ------------------------------------------------------------------------

@property

def Auth(self):

return self._auth

@Auth.setter

def Auth(self, value):

self._auth = value

# ------------------------------------------------------------------------

@property

def Socks5Addr(self):

return self._socks5Addr

@Socks5Addr.setter

def Socks5Addr(self, value):

self._socks5Addr = value

# ============================================================================

# ===( Class Response )======================================================

# ============================================================================

class _response:

# ------------------------------------------------------------------------

def __init__(self, microWebCli, socket, addr):

self._microWebCli = microWebCli

self._socket = socket

self._addr = addr

self._httpVer = None

self._code = None

self._msg = None

self._headers = {}

self._contentType = None

self._contentLength = None

self._processResponse()

# ------------------------------------------------------------------------

def _processResponse(self):

try:

self._parseFirstLine()

self._parseHeader()

if self._contentLength == 0:

self.Close()

except:

self._microWebCli.Close()

raise Exception('Error to get response')

# ------------------------------------------------------------------------

def _parseFirstLine(self):

tmp = self._socket.readline();

print(tmp)

self._httpVer, code, self._msg = tmp \

.decode() \

.strip() \

.split(' ', 2)

self._code = int(code)

# ------------------------------------------------------------------------

def _parseHeader(self):

while True:

tmp = self._socket.readline();

print(tmp)

elements = tmp \

.decode() \

.strip() \

.split(':', 1)

if len(elements) == 2:

self._headers[elements[0].strip()] = elements[1].strip()

elif len(elements) == 1 and len(elements[0]) == 0:

self._contentType = self._headers.get("Content-Type", None)

ctLen = self._headers.get("Content-Length", None)

self._contentLength = int(

ctLen) if ctLen is not None else None

break

# ------------------------------------------------------------------------

def GetClient(self):

return self._microWebCli

# ------------------------------------------------------------------------

def GetAddr(self):

return self._addr

# ------------------------------------------------------------------------

def GetIPAddr(self):

return self._addr[0]

# ------------------------------------------------------------------------

def GetPort(self):

return self._addr[1]

# ------------------------------------------------------------------------

def GetHTTPVersion(self):

return self._httpVer

# ------------------------------------------------------------------------

def GetStatusCode(self):

return self._code

# ------------------------------------------------------------------------

def GetStatusMessage(self):

return self._msg

# ------------------------------------------------------------------------

def IsSuccess(self):

return (self._code >= 200 and self._code < 300)

# ------------------------------------------------------------------------

def IsLocationMoved(self):

return self.LocationMovedURL() is not None

# ------------------------------------------------------------------------

def LocationMovedURL(self):

if self._code >= 300 and self._code < 400:

return self._headers.get('Location', None)

return None

# ------------------------------------------------------------------------

def GetHeaders(self):

return self._headers

# ------------------------------------------------------------------------

def GetContentType(self):

return self._contentType

# ------------------------------------------------------------------------

def GetContentLength(self):

return self._contentLength

# ------------------------------------------------------------------------

def ReadContent(self, size=None):

try:

if size is None:

b = self._socket.read()

self.Close()

return b

elif size > 0:

b = self._socket.read(size)

if len(b) < size:

self.Close()

return b

except MemoryError as memEx:

self.Close()

raise MemoryError('Error to read response content (%s)' %

memEx)

except:

self.Close()

return None

# ------------------------------------------------------------------------

def ReadContentInto(self, buf, nbytes=None):

if nbytes is None:

nbytes = len(buf)

if nbytes > 0:

try:

x = self._socket.readinto(buf, nbytes)

if x < nbytes:

self.Close()

return x

except Exception as e:

print(e)

self.Close()

except:

self.Close()

return 0

# ------------------------------------------------------------------------

def ReadContentAsJSON(self):

cnt = self.ReadContent()

if cnt:

if not 'json' in globals():

import json

try:

return json.loads(cnt)

except:

raise Exception('Error to parse JSON response : %s' % cnt)

return None

# ------------------------------------------------------------------------

def WriteContentToFile(self, filepath, progressCallback=None):

fSize = self._contentLength

buf = MicroWebCli._tryAllocByteArray(

fSize if fSize and fSize < 10240 else 10240)

if not buf:

raise MemoryError('Not enough memory to allocate buffer')

buf = memoryview(buf)

try:

file = open(filepath, 'wb')

except:

raise Exception('Error to create file (%s)' % filepath)

sizeRem = fSize

pgrSize = 0

#import uhashlib, binascii

#sha = uhashlib.sha256()

while sizeRem is None or sizeRem > 0:

if sizeRem and sizeRem < len(buf):

buf = buf[:sizeRem]

x = self.ReadContentInto(buf)

if x == 0:

break

if x < len(buf):

buf = buf[:x]

if sizeRem:

sizeRem -= x

#print('len(buf)', len(buf), 'buf', bytes(buf[:len(buf)-1]))

#tmp = bytes(buf[:len(buf)])

#print(tmp)

#sha.update(tmp)

try:

file.write(buf)

except Exception as ex:

print('file.write : %s' % ex)

pgrSize += x

if progressCallback:

try:

progressCallback(self, pgrSize, fSize)

except Exception as ex:

print('Error in progressCallback : %s' % ex)

file.close()

self.Close()

#print(binascii.hexlify(sha.digest()))

if sizeRem and sizeRem > 0:

if not 'remove' in globals():

from os import remove

remove(filepath)

raise Exception('Error to receive and save file (%s)' %

filepath)

# ------------------------------------------------------------------------

def IsClosed(self):

return self._microWebCli.IsClosed()

# ------------------------------------------------------------------------

def Close(self):

self._microWebCli.Close()

# ============================================================================

# ============================================================================

# ============================================================================

import network, time, socket

from machine import UART

from fpioa_manager import fm, board_info

if 'wlan' not in locals():

# En EP8285 AT

fm.register(27, fm.fpioa.UART2_TX, force=True)

fm.register(28, fm.fpioa.UART2_RX, force=True)

uart = UART(UART.UART2, 115200*8, 8, 2, 2, timeout=3000, read_buf_len=10240)

time.sleep(3)

wlan = network.ESP8285(uart)

WIFI_SSID = "webduino.io"

WIFI_PASW = "webduino"

err = 0

while 1:

try:

wlan.connect(WIFI_SSID, WIFI_PASW)

except Exception:

err += 1

print("Connect AP failed, now try again")

if err > 3:

raise Exception("Conenct AP fail")

continue

break

print(wlan.ifconfig())

print(wlan.isconnected())

def progressCallback(microWebCli, progressSize, totalSize):

if totalSize:

pass #

print('Progress: %d bytes of %d downloaded...' % (progressSize, totalSize))

else:

pass #

print('Progress: %d bytes downloaded...' % progressSize)

filename = '/sd/face_test.kmodel'

fileurl = 'http://120.78.165.108/juwan/face.kmodel'

try:

import os

os.remove(filename)

except Exception as e:

pass

contentType = MicroWebCli.FileRequest(fileurl, filename, progressCallback)

print('File of content type "%s" was saved to "%s"' % (contentType, filename))

micropython 移植k210_重构+优化 micropython 下 k210 (esp8285) 的 AT network 通信过程(附代码,顺便讲讲状态机实现)。...相关推荐

  1. micropython 移植k210_嘉楠勘智K210支持最大国产开源RTOS及MicroPython

    日前,嘉楠科技AI芯片勘智K210积极响应市场需求,即将开放对RTT-RTOS及基于RTOS应用MicroPython的支持.RTT(RealTime-Thread)是国内最大的开源实时操作系统.基于 ...

  2. linux 下载python命令_Linux下修改Python命令的方法示例(附代码)

    本篇文章给大家带来的内容是关于Linux下修改Python命令的方法示例(附代码),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. Linux默认python命令指向的是/usr/bi ...

  3. STM32 进阶教程 1 - micropython 移植

    前言 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言. Python 是一个高层次的结合了解释性.编译性.互动性和面向对象的脚本语言.具有如下特点: 1.易于学习:Python有相 ...

  4. MicroPython移植到STM32H750

    Micropython移植到STM32H750 tb上买了块stm32h750vbt6的核心板,想折腾一下,正好最近在学python,于是尝试在这块板子上移植一下micropython.然而官方貌似还 ...

  5. [长文干货]MicroPython移植到野火STM32F429开发板

    最近通过参考网上的文章,成功将MicroPython移植到野火STM32F429开发板上,给大家分享一下自己的移植过程,可以作为STM32系列移植MicroPY的参考. 1.移植前准备工作 实验环境: ...

  6. 在mm32f3270为micropython移植utime模块

    在mm32f3270为micropython移植utime模块 苏勇,2021年8月 Introduction utime模块的主要函数在"extmod"目录下的utime_mph ...

  7. micropython入门教程-我的MicroPython入门之路

    我刚工作的时候,部门花大价钱买了一套基于FPGA的实时仿真系统,编译的时候看到.py的文件生成让我很感兴趣,从此开始接触python. 后来,我利用空闲时间开始学习python,近几年正是python ...

  8. 深入探索Android卡顿优化(下)

    前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~. 在上篇文章中,笔者带领大家学习了卡顿优化分析方法与工具.自动化卡顿检测方案及优化这两块内容. ...

  9. 交通优化需求下 智能交通已达千亿市场

    交通发展对社会生活所造成的深刻影响,早已被无数历史和当下的案例所印证. 交通发展对社会生活所造成的深刻影响,早已被无数历史和当下的案例所印证.翻开中国地图,在无数条高速.国道.铁路.航空线交错之处,无 ...

  10. 通过/proc/sys/net/ipv4/优化Linux下网络性能

    通过/proc/sys/net/ipv4/优化Linux下网络性能 /proc/sys/net/ipv4/优化 1)      /proc/sys/net/ipv4/ip_forward 该文件表示是 ...

最新文章

  1. 字符串缓冲区太小怎么解决_epoll的两种模式 ET和LT printf的缓冲区问题 边缘非阻塞模式...
  2. 科大星云诗社动态20210211
  3. 凉茶里偷掺消炎药 广东两名凉茶铺经营者领刑
  4. Bootstrap 3: 使用注意box-sizing细节及解决方法
  5. 多目标跟踪——MOT数据集的学习笔记
  6. FileStream构造函数
  7. thinkphp5 mysql加1_ThinkPHP5.1的数据库链接和增删改查
  8. 中国银联全渠道系统商户接入 测试指引-银联网关支付产品
  9. 仿映客直播礼物特效制作流程
  10. Excel技能树系列05:TEXT函数,IF函数和INDEX+MATCH组合查找函数
  11. C语言基础知识入门(2022年)
  12. 思维简史:从丛林到宇宙
  13. 支付宝,微信,线上支付流程介绍
  14. SDUT OJ 2132 (一般算术表达式转换成后缀式)
  15. [P3371 ]【模板】单源最短路径
  16. MyEclipse如何配置Tomcat
  17. 常用的cmd /c和cmd /k 以及CMD命令
  18. 除了年夜饭,这些举动能够让老人暖心
  19. 金融系统监控联网报警系统!
  20. FCPX插件Alex4D Animation Transitions安装教程

热门文章

  1. 2022好用的手机库存管理软件排行榜前十名 手机库存管理app
  2. iOS 超好用的本地视频播放器推荐!
  3. 浅谈C10K问题 与 解决方案
  4. 描述 J2EE 框架的多层结构,并简要说明各层的作用。
  5. QQ音乐无损歌曲分析教程
  6. 理正深基坑弹性计算方法_理正深基坑软件应用全参数说明书
  7. cocos creator 游戏框架
  8. Windows快速更改IP脚本
  9. 总结----20个最常见的算法面试问题
  10. 超实用windows软件推荐!新电脑必装哪些软件?