Compare commits
36 Commits
090c44433f
...
hi3861
| Author | SHA1 | Date | |
|---|---|---|---|
| 16d6108292 | |||
| 30edbda7de | |||
| 3d7a4dcb4d | |||
| 6c7f013a0c | |||
| 95aa6b6bba | |||
| 739cd1d914 | |||
| 01df759772 | |||
| cb88e6fccd | |||
| 80e995b47c | |||
| f82df06a68 | |||
| dc651af561 | |||
| 9f9bd25ce7 | |||
| 97ca0d75c2 | |||
| 75cc3b8ea3 | |||
| aca5703b9e | |||
| 2eba46bc40 | |||
| f342d37d63 | |||
| 1c914cf89f | |||
| afba7af80b | |||
| 8eef0d9414 | |||
| 8e8fda7fe9 | |||
| 9879cb1547 | |||
| 3829cf76ee | |||
| c8a541ec11 | |||
| b5839d2c36 | |||
| afe15b990a | |||
| 7f89965956 | |||
| c7ecc5325e | |||
| 01b286fce1 | |||
| 85c8302fc1 | |||
| 0cd70df215 | |||
| 658560c34f | |||
| c773a12f90 | |||
| a41a4a2236 | |||
| 3d7c7a06e4 | |||
| 19176c71b7 |
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
12
.idea/License_plate_recognition.iml
generated
12
.idea/License_plate_recognition.iml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="format" value="PLAIN" />
|
|
||||||
<option name="myDocStringFormat" value="Plain" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
6
.idea/inspectionProfiles/profiles_settings.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
||||||
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" />
|
|
||||||
</project>
|
|
||||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/License_plate_recognition.iml" filepath="$PROJECT_DIR$/.idea/License_plate_recognition.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
7
.idea/vcs.xml
generated
7
.idea/vcs.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="" vcs="Git" />
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
20
BUILD.gn
Normal file
20
BUILD.gn
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
static_library("barrier_gate_client") {
|
||||||
|
sources = [
|
||||||
|
# 根据功能划分文件
|
||||||
|
"demo_entry_cmsis.c", # 入口和主线程 源文件
|
||||||
|
"udp_client_test.c", # UDP客户端测试 源文件
|
||||||
|
"oled_ssd1306.c", # oled 显示屏驱动文件
|
||||||
|
"wifi_connecter.c", # wifi
|
||||||
|
"robot_sg90.c", # sg90 舵机
|
||||||
|
"json_parser.c", # JSON解析器
|
||||||
|
"display_helper.c" # 显示辅助文件
|
||||||
|
|
||||||
|
]
|
||||||
|
|
||||||
|
include_dirs = [
|
||||||
|
"//utils/native/lite/include",
|
||||||
|
"//kernel/liteos_m/kal/cmsis",
|
||||||
|
"//base/iot_hardware/peripheral/interfaces/kits",
|
||||||
|
"//foundation/communication/wifi_lite/interfaces/wifiservice", # HAL接口中的WiFi接口
|
||||||
|
]
|
||||||
|
}
|
||||||
56
chinese_char_map.h
Normal file
56
chinese_char_map.h
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef CHINESE_CHAR_MAP_H
|
||||||
|
#define CHINESE_CHAR_MAP_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// 中文字符映射结构体
|
||||||
|
typedef struct {
|
||||||
|
const char* utf8_char; // UTF-8编码的中文字符
|
||||||
|
uint8_t font_index; // 在fonts3数组中的索引
|
||||||
|
} ChineseCharMap;
|
||||||
|
|
||||||
|
// 中文字符映射表 - 根据fonts3数组中的字符顺序
|
||||||
|
static const ChineseCharMap chinese_char_map[] = {
|
||||||
|
{"京", 0}, // ID:0 - 北京
|
||||||
|
{"沪", 1}, // ID:1 - 上海
|
||||||
|
{"津", 2}, // ID:2 - 天津
|
||||||
|
{"渝", 3}, // ID:3 - 重庆
|
||||||
|
{"冀", 4}, // ID:4 - 河北
|
||||||
|
{"晋", 5}, // ID:5 - 山西
|
||||||
|
{"蒙", 6}, // ID:6 - 内蒙古
|
||||||
|
{"辽", 7}, // ID:7 - 辽宁
|
||||||
|
{"吉", 8}, // ID:8 - 吉林
|
||||||
|
{"黑", 9}, // ID:9 - 黑龙江
|
||||||
|
{"苏", 10}, // ID:10 - 江苏
|
||||||
|
{"浙", 11}, // ID:11 - 浙江
|
||||||
|
{"皖", 12}, // ID:12 - 安徽
|
||||||
|
{"闽", 13}, // ID:13 - 福建
|
||||||
|
{"赣", 14}, // ID:14 - 江西
|
||||||
|
{"鲁", 15}, // ID:15 - 山东
|
||||||
|
{"豫", 16}, // ID:16 - 河南
|
||||||
|
{"鄂", 17}, // ID:17 - 湖北
|
||||||
|
{"湘", 18}, // ID:18 - 湖南
|
||||||
|
{"粤", 19}, // ID:19 - 广东
|
||||||
|
{"桂", 20}, // ID:20 - 广西
|
||||||
|
{"琼", 21}, // ID:21 - 海南
|
||||||
|
{"川", 22}, // ID:22 - 四川
|
||||||
|
{"贵", 23}, // ID:23 - 贵州
|
||||||
|
{"云", 24}, // ID:24 - 云南
|
||||||
|
{"藏", 25}, // ID:25 - 西藏
|
||||||
|
{"陕", 26}, // ID:26 - 陕西
|
||||||
|
{"甘", 27}, // ID:27 - 甘肃
|
||||||
|
{"青", 28}, // ID:28 - 青海
|
||||||
|
{"宁", 29}, // ID:29 - 宁夏
|
||||||
|
{"新", 30}, // ID:30 - 新疆
|
||||||
|
{"禁", 31}, // ID:31 - 禁止
|
||||||
|
{"通", 32}, // ID:32 - 通行
|
||||||
|
{"行", 33} // ID:33 - 行驶
|
||||||
|
};
|
||||||
|
|
||||||
|
// 映射表大小
|
||||||
|
#define CHINESE_CHAR_MAP_SIZE (sizeof(chinese_char_map) / sizeof(ChineseCharMap))
|
||||||
|
|
||||||
|
// 函数声明
|
||||||
|
int FindChineseCharIndex(const char* utf8_char);
|
||||||
|
|
||||||
|
#endif // CHINESE_CHAR_MAP_H
|
||||||
525
demo_entry_cmsis.c
Normal file
525
demo_entry_cmsis.c
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
#include <stdio.h> // 标准输入输出
|
||||||
|
#include <unistd.h> // POSIX标准接口
|
||||||
|
#include <string.h> // 字符串处理(操作字符数组)
|
||||||
|
|
||||||
|
#include "ohos_init.h" // 用于初始化服务(services)和功能(features)
|
||||||
|
#include "cmsis_os2.h" // CMSIS-RTOS API V2
|
||||||
|
|
||||||
|
#include "wifi_connecter.h" // easy wifi (station模式)
|
||||||
|
#include "oled_ssd1306.h" // OLED驱动接口
|
||||||
|
#include "json_parser.h"
|
||||||
|
#include "display_helper.h" // 显示辅助函数
|
||||||
|
#include "robot_sg90.h" // 舵机控制接口
|
||||||
|
#include "iot_gpio.h"
|
||||||
|
#include "hi_io.h"
|
||||||
|
#include "hi_time.h"
|
||||||
|
#include "iot_gpio.h"
|
||||||
|
#include "hi_adc.h"
|
||||||
|
#include "iot_errno.h"
|
||||||
|
#if 1
|
||||||
|
// 定义一个宏,用于标识SSID。请根据实际情况修改
|
||||||
|
#define PARAM_HOTSPOT_SSID "tarikPura"
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识密码。请根据实际情况修改
|
||||||
|
#define PARAM_HOTSPOT_PSK "66668888"
|
||||||
|
#elif
|
||||||
|
#define PARAM_HOTSPOT_SSID "DYJY"
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识密码。请根据实际情况修改
|
||||||
|
#define PARAM_HOTSPOT_PSK "12345678"
|
||||||
|
#endif
|
||||||
|
// 定义一个宏,用于标识加密方式
|
||||||
|
#define PARAM_HOTSPOT_TYPE WIFI_SEC_TYPE_PSK
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识UDP服务器IP地址。请根据实际情况修改
|
||||||
|
#define PARAM_SERVER_ADDR "192.168.43.137"
|
||||||
|
|
||||||
|
#define GPIO5 5
|
||||||
|
#define ADC_TEST_LENGTH (20)
|
||||||
|
#define VLT_MIN (100)
|
||||||
|
#define KEY_INTERRUPT_PROTECT_TIME (30)
|
||||||
|
|
||||||
|
unsigned short g_adc_buf[ADC_TEST_LENGTH] = { 0 };
|
||||||
|
unsigned short g_gpio5_adc_buf[ADC_TEST_LENGTH] = { 0 };
|
||||||
|
unsigned int g_gpio5_tick = 0;
|
||||||
|
|
||||||
|
int control_flag = 0;
|
||||||
|
extern char response[128];
|
||||||
|
extern JsonCommand g_current_command; // 外部声明JSON命令变量
|
||||||
|
|
||||||
|
// 舵机控制函数声明
|
||||||
|
extern void servo_rotate_clockwise_90(void);
|
||||||
|
extern void servo_rotate_counter_clockwise_90(void);
|
||||||
|
extern void regress_middle(void);
|
||||||
|
|
||||||
|
uint8_t fonts3[] = {
|
||||||
|
|
||||||
|
/*-- ID:0,字符:"京",ASCII编码:BEA9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x08,0x08,0x08,0xe8,0x28,0x28,0x29,0x2e,0x28,0x28,0x28,0xf8,0x28,0x0c,0x08,0x00,
|
||||||
|
0x00,0x00,0x40,0x23,0x1a,0x42,0x82,0x7e,0x02,0x0a,0x12,0x33,0x60,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:1,字符:"沪",ASCII编码:BBA6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x22,0x64,0x0c,0x80,0x00,0xf8,0x88,0x89,0x8a,0x8e,0x88,0x88,0xfc,0x08,0x00,
|
||||||
|
0x04,0x04,0xfc,0x03,0x80,0x60,0x1f,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:2,字符:"津",ASCII编码:BDF2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x20,0x42,0xc4,0x0c,0x10,0x54,0x54,0x54,0xff,0x54,0x54,0x54,0x7e,0x14,0x10,0x00,
|
||||||
|
0x04,0x04,0xfc,0x02,0x11,0x12,0x12,0x12,0xff,0x12,0x12,0x13,0x1a,0x10,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:3,字符:"渝",ASCII编码:D3E5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x22,0x64,0x0c,0xa0,0xd0,0x48,0x54,0xd2,0x13,0x94,0x08,0xd0,0x30,0x10,0x00,
|
||||||
|
0x04,0x04,0xfe,0x01,0x00,0xff,0x12,0x92,0xff,0x00,0x5f,0x80,0x7f,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:4,字符:"冀",ASCII编码:BCBD,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x08,0x0a,0xea,0xaa,0xaa,0xaa,0xaf,0xe0,0xaf,0xaa,0xaa,0xaa,0xfa,0x28,0x0c,0x00,
|
||||||
|
0x20,0xa0,0xab,0x6a,0x2a,0x3e,0x2a,0x2b,0x2a,0x3e,0x2a,0x6a,0xab,0xa0,0x20,0x00,
|
||||||
|
|
||||||
|
/*-- ID:5,字符:"晋",ASCII编码:BDFA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x42,0x4a,0x52,0x42,0x7e,0x42,0x42,0x42,0x7e,0x42,0xd2,0x4b,0x62,0x40,0x00,
|
||||||
|
0x00,0x00,0x00,0xff,0x49,0x49,0x49,0x49,0x49,0x49,0x49,0xff,0x01,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:6,字符:"蒙",ASCII编码:C3C9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x20,0x9a,0x8a,0x8a,0xaa,0xaf,0xaa,0xaa,0xaa,0xaf,0xaa,0x8a,0x8b,0xaa,0x18,0x00,
|
||||||
|
0x00,0x50,0x52,0x2a,0x2a,0x15,0x4b,0x86,0x7c,0x04,0x0a,0x13,0x20,0x60,0x20,0x00,
|
||||||
|
|
||||||
|
/*-- ID:7,字符:"辽",ASCII编码:C1C9,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0x21,0x22,0xe6,0x00,0x02,0x02,0x02,0x02,0xf2,0x12,0x0a,0x06,0x02,0x00,0x00,
|
||||||
|
0x00,0x40,0x20,0x1f,0x20,0x40,0x40,0x48,0x50,0x4f,0x40,0x40,0x40,0x60,0x20,0x00,
|
||||||
|
|
||||||
|
/*-- ID:8,字符:"吉",ASCII编码:BCAA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x08,0x08,0x88,0x88,0x88,0x88,0x88,0xff,0x88,0x88,0x88,0xc8,0x88,0x0c,0x08,0x00,
|
||||||
|
0x00,0x00,0x00,0xfc,0x44,0x44,0x44,0x44,0x44,0x44,0x44,0xfe,0x04,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:9,字符:"黑",ASCII编码:BADA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0x00,0xfe,0x8a,0x92,0xb2,0x82,0xfe,0x82,0xa2,0x9a,0x92,0xff,0x02,0x00,0x00,
|
||||||
|
0x08,0x8a,0x6a,0x0a,0x2a,0xca,0x0a,0x0f,0x2a,0xca,0x0a,0x2a,0x4a,0xca,0x08,0x00,
|
||||||
|
|
||||||
|
/*-- ID:10,字符:"苏",ASCII编码:CBD5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x04,0x04,0x44,0x44,0x5f,0x44,0xf4,0x44,0x44,0x44,0x5f,0xe4,0x44,0x06,0x04,0x00,
|
||||||
|
0x00,0x88,0x46,0x20,0x10,0x0c,0x03,0x00,0x00,0x40,0x80,0x7f,0x02,0x04,0x0c,0x00,
|
||||||
|
|
||||||
|
/*-- ID:11,字符:"浙",ASCII编码:D5E3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x22,0x64,0x0c,0x90,0x10,0xff,0x10,0x90,0xfc,0x44,0x44,0xc2,0x62,0x40,0x00,
|
||||||
|
0x04,0x04,0xfe,0x01,0x42,0x82,0x7f,0x41,0x20,0x1f,0x00,0x00,0xff,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:12,字符:"皖",ASCII编码:CDEE,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0xf8,0x8c,0x8b,0x88,0xf8,0x10,0x0c,0x24,0x25,0x26,0x34,0x24,0x94,0x0c,0x00,
|
||||||
|
0x00,0x3f,0x10,0x10,0x10,0xbf,0x41,0x31,0x0f,0x01,0x01,0x3f,0x41,0x41,0x71,0x00,
|
||||||
|
|
||||||
|
/*-- ID:13,字符:"闽",ASCII编码:C3F6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0xfc,0x01,0x02,0xe6,0x20,0x22,0xfa,0x22,0x22,0xf2,0x22,0x02,0xff,0x02,0x00,
|
||||||
|
0x00,0xff,0x00,0x20,0x27,0x22,0x22,0x3f,0x12,0x12,0x1b,0x70,0x80,0x7f,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:14,字符:"赣",ASCII编码:B8D3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x12,0xd6,0x5a,0x53,0x5a,0xd6,0x12,0x28,0x64,0x57,0xca,0x56,0x52,0x20,0x20,0x00,
|
||||||
|
0x10,0x17,0x15,0xfd,0x15,0x17,0x10,0x81,0xbd,0x45,0x35,0x45,0x7d,0x81,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:15,字符:"鲁",ASCII编码:C2B3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x10,0xf8,0xac,0xaa,0xab,0xaa,0xfa,0xae,0xaa,0xaa,0xa8,0xfc,0x08,0x00,0x00,
|
||||||
|
0x02,0x02,0x02,0xfa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xfe,0x0a,0x02,0x02,0x00,
|
||||||
|
|
||||||
|
/*-- ID:16,字符:"豫",ASCII编码:D4A5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x42,0x52,0xe2,0x5a,0xc6,0x50,0xf8,0x94,0xd3,0xba,0x96,0x92,0xf8,0x10,0x00,
|
||||||
|
0x00,0x40,0x80,0x7f,0x00,0x10,0x54,0x4a,0x25,0x92,0xfc,0x0c,0x12,0x61,0x20,0x00,
|
||||||
|
|
||||||
|
/*-- ID:17,字符:"鄂",ASCII编码:B6F5,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0x5e,0x52,0x5e,0x40,0x5e,0x52,0x5e,0x00,0xfe,0x02,0x42,0xb2,0x0e,0x00,0x00,
|
||||||
|
0x01,0x01,0x0d,0x4b,0x89,0x89,0x7d,0x09,0x01,0xff,0x08,0x10,0x20,0x11,0x0e,0x00,
|
||||||
|
|
||||||
|
/*-- ID:18,字符:"湘",ASCII编码:CFE6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x22,0x64,0x0c,0x10,0xd0,0xff,0x90,0x10,0xfc,0x44,0x44,0x44,0xfe,0x04,0x00,
|
||||||
|
0x04,0x04,0xfe,0x05,0x03,0x00,0xff,0x00,0x01,0xff,0x44,0x44,0x44,0xff,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:19,字符:"粤",ASCII编码:D4C1,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0x00,0x00,0xfe,0x92,0xd6,0x93,0xfe,0x92,0xd6,0x92,0xff,0x02,0x00,0x00,0x00,
|
||||||
|
0x02,0x02,0x02,0x02,0x0a,0x0e,0x0a,0x0a,0x4a,0x8a,0x4a,0x3a,0x02,0x03,0x02,0x00,
|
||||||
|
|
||||||
|
/*-- ID:20,字符:"桂",ASCII编码:B9F0,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x10,0xd0,0xff,0x90,0x50,0x48,0x48,0x48,0xff,0x48,0x48,0x4c,0x68,0x40,0x00,
|
||||||
|
0x04,0x03,0x00,0xff,0x40,0x41,0x44,0x44,0x44,0x7f,0x44,0x44,0x46,0x64,0x40,0x00,
|
||||||
|
|
||||||
|
/*-- ID:21,字符:"琼",ASCII编码:C7ED,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x44,0x44,0xfc,0x46,0x44,0x08,0xe8,0x28,0x29,0x2a,0x28,0x28,0xf8,0x2c,0x08,0x00,
|
||||||
|
0x10,0x30,0x1f,0x08,0x08,0x20,0x13,0x5a,0x82,0x7e,0x02,0x0a,0x13,0x30,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:22,字符:"川",ASCII编码:B4A8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0x00,0x00,0xfe,0x00,0x00,0x00,0x00,0xfc,0x00,0x00,0x00,0x00,0xff,0x00,0x00,
|
||||||
|
0x00,0x40,0x20,0x1f,0x00,0x00,0x00,0x00,0x1f,0x00,0x00,0x00,0x00,0xff,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:23,字符:"贵",ASCII编码:B9F3,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x40,0x5c,0x54,0x54,0x54,0x54,0x7f,0x54,0x54,0x54,0xd4,0x5e,0x44,0x40,0x00,
|
||||||
|
0x00,0x00,0x80,0x9f,0x41,0x41,0x21,0x1d,0x21,0x21,0x41,0x5f,0x81,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:24,字符:"云",ASCII编码:D4C6,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x40,0x40,0x44,0x44,0x44,0xc4,0x44,0x44,0x44,0x46,0x44,0x40,0x60,0x40,0x00,
|
||||||
|
0x00,0x00,0x40,0x60,0x58,0x46,0x41,0x40,0x40,0x40,0x50,0x60,0xc0,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:25,字符:"藏",ASCII编码:B2D8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x02,0xf2,0x82,0xf2,0x12,0xdf,0x52,0xd2,0x5f,0x12,0xfe,0x12,0x16,0x9b,0x12,0x00,
|
||||||
|
0x90,0x4e,0x22,0x1f,0x00,0x7f,0x25,0x3d,0xa7,0x40,0x2f,0x30,0x4c,0x83,0xe0,0x00,
|
||||||
|
|
||||||
|
/*-- ID:26,字符:"陕",ASCII编码:C9C2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0xfe,0x22,0x5a,0x86,0x08,0x28,0x48,0x08,0xff,0x08,0x48,0x2c,0x88,0x00,0x00,
|
||||||
|
0x00,0xff,0x04,0x08,0x87,0x41,0x21,0x11,0x0d,0x03,0x0d,0x11,0x61,0xc1,0x41,0x00,
|
||||||
|
|
||||||
|
/*-- ID:27,字符:"甘",ASCII编码:B8CA,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x10,0x10,0x10,0x10,0xff,0x10,0x10,0x10,0x10,0x10,0xff,0x10,0x10,0x18,0x10,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,0xff,0x42,0x42,0x42,0x42,0x42,0xff,0x00,0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:28,字符:"青",ASCII编码:C7E0,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x44,0x54,0x54,0x54,0x54,0x54,0x7f,0x54,0x54,0x54,0xd4,0x56,0x44,0x40,0x00,
|
||||||
|
0x00,0x00,0x00,0xff,0x15,0x15,0x15,0x15,0x15,0x55,0x95,0x7f,0x01,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:29,字符:"宁",ASCII编码:C4FE,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x50,0x4c,0x44,0x44,0x44,0x44,0x45,0xc6,0x44,0x44,0x44,0x44,0x44,0x54,0x4c,0x00,
|
||||||
|
0x00,0x00,0x00,0x00,0x00,0x40,0x80,0x7f,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:30,字符:"新",ASCII编码:D0C2,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x44,0x54,0x65,0xc6,0x64,0xd6,0x44,0x40,0xfc,0x44,0x42,0xc3,0x62,0x40,0x00,
|
||||||
|
0x20,0x11,0x49,0x81,0x7f,0x01,0x05,0x29,0x18,0x07,0x00,0x00,0xff,0x00,0x00,0x00,
|
||||||
|
|
||||||
|
/*-- ID:31,字符:"禁",ASCII编码:BDFB,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x24,0x14,0x0c,0x7f,0x14,0x24,0x20,0x14,0x0c,0x7f,0x0c,0x16,0x24,0x40,0x00,
|
||||||
|
0x04,0x04,0x45,0x25,0x15,0x45,0x85,0x7d,0x05,0x05,0x15,0x25,0x65,0x04,0x04,0x00,
|
||||||
|
|
||||||
|
/*-- ID:32,字符:"通",ASCII编码:CDA8,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x40,0x42,0x44,0xcc,0x00,0x00,0xf1,0x91,0x95,0xf9,0x95,0x93,0xf9,0x10,0x00,0x00,
|
||||||
|
0x00,0x40,0x20,0x1f,0x20,0x40,0xbf,0x84,0x84,0xbf,0x94,0xa4,0x9f,0xc0,0x40,0x00,
|
||||||
|
|
||||||
|
/*-- ID:33,字符:"行",ASCII编码:D0D0,对应字:宽x高=16x16,画布:宽W=16 高H=16,共32字节*/
|
||||||
|
0x00,0x10,0x88,0xc4,0x23,0x40,0x42,0x42,0x42,0x42,0x42,0xc2,0x43,0x62,0x40,0x00,
|
||||||
|
0x02,0x01,0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x40,0x80,0x7f,0x00,0x00,0x00,0x00};
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识UDP服务器端口
|
||||||
|
#define PARAM_SERVER_PORT 8081
|
||||||
|
|
||||||
|
void switch_init(void)
|
||||||
|
{
|
||||||
|
IoTGpioInit(5);
|
||||||
|
hi_io_set_func(5, 0);
|
||||||
|
IoTGpioSetDir(5, IOT_GPIO_DIR_IN);
|
||||||
|
hi_io_set_pull(5, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//按键中断响应函数
|
||||||
|
void gpio5_isr_func_mode(void)
|
||||||
|
{
|
||||||
|
printf("gpio5_isr_func_mode start\n");
|
||||||
|
unsigned int tick_interval = 0;
|
||||||
|
unsigned int current_gpio5_tick = 0;
|
||||||
|
|
||||||
|
current_gpio5_tick = hi_get_tick();
|
||||||
|
tick_interval = current_gpio5_tick - g_gpio5_tick;
|
||||||
|
|
||||||
|
if (tick_interval < KEY_INTERRUPT_PROTECT_TIME) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_gpio5_tick = current_gpio5_tick;
|
||||||
|
control_flag = !control_flag;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char get_gpio5_voltage(void *param)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
unsigned short data;
|
||||||
|
unsigned int ret;
|
||||||
|
unsigned short vlt;
|
||||||
|
float voltage;
|
||||||
|
float vlt_max = 0;
|
||||||
|
float vlt_min = VLT_MIN;
|
||||||
|
|
||||||
|
hi_unref_param(param);
|
||||||
|
memset_s(g_gpio5_adc_buf, sizeof(g_gpio5_adc_buf), 0x0, sizeof(g_gpio5_adc_buf));
|
||||||
|
for (i = 0; i < ADC_TEST_LENGTH; i++) {
|
||||||
|
ret = hi_adc_read(HI_ADC_CHANNEL_2, &data, HI_ADC_EQU_MODEL_4, HI_ADC_CUR_BAIS_DEFAULT, 0xF0);
|
||||||
|
//ADC_Channal_2 自动识别模式 CNcomment:4次平均算法模式 CNend */
|
||||||
|
if (ret != IOT_SUCCESS) {
|
||||||
|
printf("ADC Read Fail\n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
g_gpio5_adc_buf[i] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ADC_TEST_LENGTH; i++) {
|
||||||
|
vlt = g_gpio5_adc_buf[i];
|
||||||
|
voltage = (float)vlt * 1.8 * 4 / 4096.0;
|
||||||
|
/* vlt * 1.8* 4 / 4096.0为将码字转换为电压 */
|
||||||
|
vlt_max = (voltage > vlt_max) ? voltage : vlt_max;
|
||||||
|
vlt_min = (voltage < vlt_min) ? voltage : vlt_min;
|
||||||
|
}
|
||||||
|
printf("vlt_max is %f\r\n", vlt_max);
|
||||||
|
if (vlt_max > 0.6 && vlt_max < 1.0) {
|
||||||
|
gpio5_isr_func_mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//按键中断
|
||||||
|
void interrupt_monitor(void)
|
||||||
|
{
|
||||||
|
unsigned int ret = 0;
|
||||||
|
/*gpio5 switch2 mode*/
|
||||||
|
g_gpio5_tick = hi_get_tick();
|
||||||
|
ret = IoTGpioRegisterIsrFunc(GPIO5, IOT_INT_TYPE_EDGE, IOT_GPIO_EDGE_FALL_LEVEL_LOW, get_gpio5_voltage, NULL);
|
||||||
|
if (ret == IOT_SUCCESS) {
|
||||||
|
printf(" register gpio5\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void OledShowChinese3(uint8_t x, uint8_t y, uint8_t idx)
|
||||||
|
{
|
||||||
|
// 控制循环
|
||||||
|
uint8_t t;
|
||||||
|
|
||||||
|
// 显示汉字的上半部分
|
||||||
|
OledSetPosition(x, y);
|
||||||
|
for (t = 0; t < 16; t++)
|
||||||
|
{
|
||||||
|
WriteData(fonts3[32 * idx + t]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示汉字的下半部分
|
||||||
|
OledSetPosition(x, y + 1);
|
||||||
|
for (t = 16; t < 32; t++)
|
||||||
|
{
|
||||||
|
WriteData(fonts3[32 * idx + t]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void controlTask(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
int control_temp = control_flag;
|
||||||
|
int count = 0;
|
||||||
|
static int display_timer = 0; // 显示计时器
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
if(control_flag != control_temp)
|
||||||
|
{
|
||||||
|
count = 0;
|
||||||
|
display_timer = 0;
|
||||||
|
OledClearScreen(); // 使用新的清屏函数
|
||||||
|
control_temp = control_flag;
|
||||||
|
|
||||||
|
// 处理新的JSON命令格式
|
||||||
|
if(control_flag == CMD_ROTATE_DISPLAY_CLEAR) // 命令1:顺时针90°+显示字符串+10秒后逆时针90°+清屏
|
||||||
|
{
|
||||||
|
printf("Command 1: Rotate clockwise, display text, wait 10s, rotate back, clear\r\n");
|
||||||
|
|
||||||
|
// 顺时针旋转90度
|
||||||
|
servo_rotate_clockwise_90();
|
||||||
|
|
||||||
|
// 显示字符串
|
||||||
|
if (strlen(g_current_command.text) > 0) {
|
||||||
|
printf("Displaying text: %s\r\n", g_current_command.text);
|
||||||
|
DisplayMixedString(0, 0, g_current_command.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
display_timer = 10; // 设置10秒计时器
|
||||||
|
}
|
||||||
|
else if(control_flag == CMD_ROTATE_CLOCKWISE) // 命令2:顺时针90°
|
||||||
|
{
|
||||||
|
printf("Command 2: Rotate clockwise 90 degrees\r\n");
|
||||||
|
servo_rotate_clockwise_90();
|
||||||
|
}
|
||||||
|
else if(control_flag == CMD_ROTATE_COUNTER) // 命令3:逆时针90°
|
||||||
|
{
|
||||||
|
printf("Command 3: Rotate counter-clockwise 90 degrees\r\n");
|
||||||
|
servo_rotate_counter_clockwise_90();
|
||||||
|
}
|
||||||
|
else if(control_flag == CMD_DISPLAY_ONLY) // 命令4:只显示字符串,舵机不动
|
||||||
|
{
|
||||||
|
printf("Command 4: Display text only, no servo movement\r\n");
|
||||||
|
|
||||||
|
// 只显示字符串,不控制舵机
|
||||||
|
if (strlen(g_current_command.text) > 0) {
|
||||||
|
printf("Displaying text: %s\r\n", g_current_command.text);
|
||||||
|
DisplayMixedString(0, 0, g_current_command.text);
|
||||||
|
}
|
||||||
|
|
||||||
|
display_timer = 2; // 设置2秒计时器
|
||||||
|
}
|
||||||
|
// 兼容旧命令的处理逻辑(只有在非JSON命令时才执行)
|
||||||
|
else if(control_flag == 0 && g_current_command.cmd == 0) // 兼容旧命令:关闭
|
||||||
|
{
|
||||||
|
regress_middle();
|
||||||
|
printf("barrier off\n");
|
||||||
|
}
|
||||||
|
else if(control_flag == 1 && g_current_command.cmd == 0) // 兼容旧命令:开启(只有非JSON命令时)
|
||||||
|
{
|
||||||
|
servo_rotate_counter_clockwise_90();
|
||||||
|
printf("barrier on\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的复杂字符串解析逻辑 - 只有在非JSON命令时才执行
|
||||||
|
if(control_flag == 2 && g_current_command.cmd == 0)
|
||||||
|
{
|
||||||
|
char prefix[20]; // 存储前部分字符串
|
||||||
|
int num; // 存储后部分数字
|
||||||
|
int index1 = 0;
|
||||||
|
|
||||||
|
// 使用 sscanf 解析字符串
|
||||||
|
int result = sscanf(response, "%[^i]index:%dflag:%d", prefix, &num,&index1);
|
||||||
|
|
||||||
|
if (result == 3) {
|
||||||
|
memset(response,0,sizeof(response));
|
||||||
|
OledShowChinese3(0,0,num);
|
||||||
|
OledShowString(18, 0, prefix, FONT8x16);
|
||||||
|
OledShowChinese3(0 , 2, index1);
|
||||||
|
OledShowChinese3(18 , 2, 33);
|
||||||
|
}
|
||||||
|
if (index1 == 32)
|
||||||
|
{
|
||||||
|
servo_rotate_counter_clockwise_90();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理命令1的10秒计时器
|
||||||
|
if(control_flag == CMD_ROTATE_DISPLAY_CLEAR && display_timer > 0)
|
||||||
|
{
|
||||||
|
display_timer--;
|
||||||
|
if(display_timer == 0)
|
||||||
|
{
|
||||||
|
printf("10 seconds elapsed, rotating back and clearing screen\r\n");
|
||||||
|
// 逆时针旋转90度
|
||||||
|
servo_rotate_counter_clockwise_90();
|
||||||
|
// 清屏
|
||||||
|
OledClearScreen();
|
||||||
|
// 重置控制标志
|
||||||
|
control_flag = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理命令4的2秒计时器
|
||||||
|
if(control_flag == CMD_DISPLAY_ONLY && display_timer > 0)
|
||||||
|
{
|
||||||
|
display_timer--;
|
||||||
|
if(display_timer == 0)
|
||||||
|
{
|
||||||
|
printf("2 seconds elapsed, clearing screen for command 4\r\n");
|
||||||
|
// 清屏
|
||||||
|
OledClearScreen();
|
||||||
|
// 重置控制标志,使其能够响应下一个命令4
|
||||||
|
control_flag = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 兼容旧的计时逻辑
|
||||||
|
if(control_flag == 2 && g_current_command.cmd == 0)
|
||||||
|
{
|
||||||
|
count++;
|
||||||
|
if(count > 10)
|
||||||
|
{
|
||||||
|
control_flag = 0;
|
||||||
|
count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 主线程函数
|
||||||
|
static void NetDemoTask(void *arg)
|
||||||
|
{
|
||||||
|
(void)arg;
|
||||||
|
|
||||||
|
int control_temp = 0;
|
||||||
|
// 定义热点配置
|
||||||
|
WifiDeviceConfig config = {0};
|
||||||
|
|
||||||
|
// 设置热点配置中的SSID
|
||||||
|
strcpy(config.ssid, PARAM_HOTSPOT_SSID);
|
||||||
|
|
||||||
|
// 设置热点配置中的密码
|
||||||
|
strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
|
||||||
|
|
||||||
|
// 设置热点配置中的加密方式(Wi-Fi security types)
|
||||||
|
config.securityType = PARAM_HOTSPOT_TYPE;
|
||||||
|
|
||||||
|
// 显示启动信息
|
||||||
|
printf("=== Hi3861 智能闸机控制系统启动 ===\r\n");
|
||||||
|
printf("正在连接WiFi: %s\r\n", PARAM_HOTSPOT_SSID);
|
||||||
|
printf("服务器地址: %s:%d\r\n", PARAM_SERVER_ADDR, PARAM_SERVER_PORT);
|
||||||
|
|
||||||
|
// 在OLED上显示启动信息
|
||||||
|
OledFillScreen(0x00);
|
||||||
|
OledShowString(0, 0, "Starting...", FONT8x16);
|
||||||
|
OledShowString(0, 2, PARAM_HOTSPOT_SSID, FONT8x16);
|
||||||
|
|
||||||
|
// 等待100ms
|
||||||
|
osDelay(10);
|
||||||
|
|
||||||
|
// 连接到热点
|
||||||
|
printf("开始连接WiFi...\r\n");
|
||||||
|
int netId = ConnectToHotspot(&config);
|
||||||
|
|
||||||
|
// 检查是否成功连接到热点
|
||||||
|
if (netId < 0)
|
||||||
|
{
|
||||||
|
// 连接到热点失败
|
||||||
|
printf("WiFi连接失败!错误代码: %d\r\n", netId);
|
||||||
|
OledFillScreen(0x00);
|
||||||
|
OledShowString(0, 0, "WiFi Failed!", FONT8x16);
|
||||||
|
OledShowString(0, 2, "Check Config", FONT8x16);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接到热点成功,显示连接成功信息
|
||||||
|
printf("WiFi连接成功!网络ID: %d\r\n", netId);
|
||||||
|
OledFillScreen(0x00);
|
||||||
|
OledShowString(0, 0, "WiFi Connected", FONT8x16);
|
||||||
|
OledShowString(0, 2, "Starting UDP...", FONT8x16);
|
||||||
|
|
||||||
|
// 等待一段时间确保连接稳定
|
||||||
|
printf("等待网络稳定...\r\n");
|
||||||
|
sleep(3);
|
||||||
|
|
||||||
|
// 运行UDP客户端测试,发送IP地址到服务器
|
||||||
|
printf("启动UDP客户端,连接服务器 %s:%d\r\n", PARAM_SERVER_ADDR, PARAM_SERVER_PORT);
|
||||||
|
UdpClientTest(PARAM_SERVER_ADDR, PARAM_SERVER_PORT);
|
||||||
|
|
||||||
|
// 断开热点连接
|
||||||
|
printf("断开WiFi连接...\r\n");
|
||||||
|
DisconnectWithHotspot(netId);
|
||||||
|
printf("WiFi连接已断开!\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 入口函数
|
||||||
|
static void NetDemoEntry(void)
|
||||||
|
{
|
||||||
|
switch_init();
|
||||||
|
interrupt_monitor();
|
||||||
|
// 初始化OLED
|
||||||
|
OledInit();
|
||||||
|
|
||||||
|
// 全屏填充黑色
|
||||||
|
OledFillScreen(0x00);
|
||||||
|
|
||||||
|
// OLED显示APP标题
|
||||||
|
OledShowString(0, 0, "UdpClient Test", FONT8x16);
|
||||||
|
|
||||||
|
|
||||||
|
// 定义线程属性
|
||||||
|
osThreadAttr_t attr;
|
||||||
|
attr.name = "NetDemoTask";
|
||||||
|
attr.attr_bits = 0U;
|
||||||
|
attr.cb_mem = NULL;
|
||||||
|
attr.cb_size = 0U;
|
||||||
|
attr.stack_mem = NULL;
|
||||||
|
attr.stack_size = 10240;
|
||||||
|
attr.priority = osPriorityNormal;
|
||||||
|
// 创建线程
|
||||||
|
if (osThreadNew(NetDemoTask, NULL, &attr) == NULL)
|
||||||
|
{
|
||||||
|
printf("[NetDemoEntry] Falied to create NetDemoTask!\n");
|
||||||
|
}
|
||||||
|
attr.name = "controlTask";
|
||||||
|
attr.stack_size = 2048;
|
||||||
|
if (osThreadNew(controlTask, NULL, &attr) == NULL)
|
||||||
|
{
|
||||||
|
printf("[control] Falied to create NetDemoTask!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 运行入口函数
|
||||||
|
SYS_RUN(NetDemoEntry);
|
||||||
64
display_helper.c
Normal file
64
display_helper.c
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
#include "display_helper.h"
|
||||||
|
#include "chinese_char_map.h"
|
||||||
|
#include "json_parser.h"
|
||||||
|
#include "oled_ssd1306.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
// 显示混合字符串(中文+英文数字)
|
||||||
|
void DisplayMixedString(uint8_t start_x, uint8_t start_y, const char* text) {
|
||||||
|
if (!text) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t x = start_x;
|
||||||
|
uint8_t y = start_y;
|
||||||
|
int i = 0;
|
||||||
|
int text_len = strlen(text);
|
||||||
|
|
||||||
|
printf("DisplayMixedString: Processing text '%s' (length: %d)\r\n", text, text_len);
|
||||||
|
|
||||||
|
while (i < text_len) {
|
||||||
|
// 检查是否为UTF-8中文字符(通常以0xE开头的3字节序列)
|
||||||
|
if ((unsigned char)text[i] >= 0xE0 && i + 2 < text_len) {
|
||||||
|
// 提取3字节的UTF-8中文字符
|
||||||
|
char chinese_char[4] = {0};
|
||||||
|
chinese_char[0] = text[i];
|
||||||
|
chinese_char[1] = text[i + 1];
|
||||||
|
chinese_char[2] = text[i + 2];
|
||||||
|
chinese_char[3] = '\0';
|
||||||
|
|
||||||
|
printf("Found Chinese char: %02X %02X %02X\r\n",
|
||||||
|
(unsigned char)chinese_char[0],
|
||||||
|
(unsigned char)chinese_char[1],
|
||||||
|
(unsigned char)chinese_char[2]);
|
||||||
|
|
||||||
|
// 查找字符在fonts3数组中的索引
|
||||||
|
int font_index = FindChineseCharIndex(chinese_char);
|
||||||
|
if (font_index >= 0) {
|
||||||
|
printf("Displaying Chinese char at index %d, position (%d, %d)\r\n", font_index, x, y);
|
||||||
|
OledShowChinese3(x, y, font_index);
|
||||||
|
x += 16; // 中文字符宽度为16像素
|
||||||
|
} else {
|
||||||
|
printf("Chinese char not found in font table, displaying as '?'\r\n");
|
||||||
|
// 如果找不到字符,显示问号
|
||||||
|
OledShowString(x, y, "?", FONT8x16);
|
||||||
|
x += 8; // 英文字符宽度为8像素
|
||||||
|
}
|
||||||
|
i += 3; // 跳过3字节的UTF-8字符
|
||||||
|
} else {
|
||||||
|
// 处理ASCII字符(英文、数字、符号)
|
||||||
|
char ascii_char[2] = {text[i], '\0'};
|
||||||
|
printf("Found ASCII char: '%c' (0x%02X)\r\n", text[i], (unsigned char)text[i]);
|
||||||
|
OledShowString(x, y, ascii_char, FONT8x16);
|
||||||
|
x += 8; // 英文字符宽度为8像素
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否需要换行(假设屏幕宽度为128像素)
|
||||||
|
if (x >= 120) {
|
||||||
|
x = start_x;
|
||||||
|
y += 2; // 每行高度为2个单位(16像素)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
display_helper.h
Normal file
9
display_helper.h
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef DISPLAY_HELPER_H
|
||||||
|
#define DISPLAY_HELPER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// 显示混合字符串(中文+英文数字)
|
||||||
|
void DisplayMixedString(uint8_t start_x, uint8_t start_y, const char* text);
|
||||||
|
|
||||||
|
#endif // DISPLAY_HELPER_H
|
||||||
72
json_parser.c
Normal file
72
json_parser.c
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#include "json_parser.h"
|
||||||
|
#include "chinese_char_map.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// 简单的JSON解析函数,解析格式: {"cmd":1,"text":"hello"}
|
||||||
|
int ParseJsonCommand(const char* json_str, JsonCommand* command) {
|
||||||
|
if (!json_str || !command) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化结构体
|
||||||
|
command->cmd = 0;
|
||||||
|
memset(command->text, 0, MAX_TEXT_LENGTH);
|
||||||
|
|
||||||
|
// 查找cmd字段
|
||||||
|
const char* cmd_pos = strstr(json_str, "\"cmd\":");
|
||||||
|
if (cmd_pos) {
|
||||||
|
cmd_pos += 6; // 跳过"cmd":
|
||||||
|
// 跳过空格
|
||||||
|
while (*cmd_pos == ' ') cmd_pos++;
|
||||||
|
command->cmd = atoi(cmd_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找text字段
|
||||||
|
const char* text_pos = strstr(json_str, "\"text\":");
|
||||||
|
if (text_pos) {
|
||||||
|
text_pos += 7; // 跳过"text":
|
||||||
|
// 跳过空格和引号
|
||||||
|
while (*text_pos == ' ' || *text_pos == '\"') text_pos++;
|
||||||
|
|
||||||
|
// 复制文本直到遇到引号或字符串结束
|
||||||
|
int i = 0;
|
||||||
|
while (*text_pos && *text_pos != '\"' && i < MAX_TEXT_LENGTH - 1) {
|
||||||
|
command->text[i++] = *text_pos++;
|
||||||
|
}
|
||||||
|
command->text[i] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建IP地址消息,格式: {"type":"ip","address":"192.168.1.100"}
|
||||||
|
int CreateIpMessage(char* buffer, int buffer_size, const char* ip_address) {
|
||||||
|
if (!buffer || !ip_address || buffer_size < 50) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = snprintf(buffer, buffer_size,
|
||||||
|
"{\"type\":\"ip\",\"address\": \"%s\"}",
|
||||||
|
ip_address);
|
||||||
|
|
||||||
|
return (len > 0 && len < buffer_size) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找中文字符在fonts3数组中的索引
|
||||||
|
int FindChineseCharIndex(const char* utf8_char) {
|
||||||
|
if (!utf8_char) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历映射表查找匹配的字符
|
||||||
|
for (int i = 0; i < CHINESE_CHAR_MAP_SIZE; i++) {
|
||||||
|
if (strcmp(utf8_char, chinese_char_map[i].utf8_char) == 0) {
|
||||||
|
return chinese_char_map[i].font_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未找到匹配的字符
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
29
json_parser.h
Normal file
29
json_parser.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef JSON_PARSER_H
|
||||||
|
#define JSON_PARSER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// 定义命令类型
|
||||||
|
#define CMD_ROTATE_DISPLAY_CLEAR 1 // 顺时针90°+显示字符串+10秒后逆时针90°+清屏
|
||||||
|
#define CMD_ROTATE_CLOCKWISE 2 // 顺时针90°
|
||||||
|
#define CMD_ROTATE_COUNTER 3 // 逆时针90°
|
||||||
|
#define CMD_DISPLAY_ONLY 4 // 只显示字符串,舵机不动
|
||||||
|
|
||||||
|
// 定义最大字符串长度
|
||||||
|
#define MAX_TEXT_LENGTH 64
|
||||||
|
|
||||||
|
// JSON命令结构体
|
||||||
|
typedef struct {
|
||||||
|
int cmd; // 命令类型
|
||||||
|
char text[MAX_TEXT_LENGTH]; // 显示文本
|
||||||
|
} JsonCommand;
|
||||||
|
|
||||||
|
// 函数声明
|
||||||
|
int ParseJsonCommand(const char* json_str, JsonCommand* command);
|
||||||
|
// 创建IP地址消息,格式: {"type":"ip","address":"192.168.1.100"}
|
||||||
|
int CreateIpMessage(char* buffer, int buffer_size, const char* ip_address);
|
||||||
|
|
||||||
|
// 查找中文字符在fonts3数组中的索引
|
||||||
|
int FindChineseCharIndex(const char* utf8_char);
|
||||||
|
|
||||||
|
#endif // JSON_PARSER_H
|
||||||
211
oled_fonts.h
Normal file
211
oled_fonts.h
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
// 字库头文件
|
||||||
|
|
||||||
|
// 定义条件编译宏,防止头文件的重复包含和编译
|
||||||
|
#ifndef OLOED_FONTS_H
|
||||||
|
#define OLOED_FONTS_H
|
||||||
|
|
||||||
|
/************************************6*8的点阵************************************/
|
||||||
|
// 取模方式:纵向8点下高位
|
||||||
|
// 采用N*6的二维数组
|
||||||
|
// 第一维表示字符
|
||||||
|
// 每个字符对应第二维的6个数组元素,每个数组元素1字节,表示1列像素,一共6列8行
|
||||||
|
static unsigned char F6x8[][6] =
|
||||||
|
{
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 空格
|
||||||
|
{ 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // !
|
||||||
|
{ 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // "
|
||||||
|
{ 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // #
|
||||||
|
{ 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $
|
||||||
|
{ 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // %
|
||||||
|
{ 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // &
|
||||||
|
{ 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // '
|
||||||
|
{ 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // (
|
||||||
|
{ 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // )
|
||||||
|
{ 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // *
|
||||||
|
{ 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // +
|
||||||
|
{ 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // ,
|
||||||
|
{ 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // -
|
||||||
|
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // .
|
||||||
|
{ 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // /
|
||||||
|
{ 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0
|
||||||
|
{ 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1
|
||||||
|
{ 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2
|
||||||
|
{ 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3
|
||||||
|
{ 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4
|
||||||
|
{ 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5
|
||||||
|
{ 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6
|
||||||
|
{ 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7
|
||||||
|
{ 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8
|
||||||
|
{ 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9
|
||||||
|
{ 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // :
|
||||||
|
{ 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 }, // ;
|
||||||
|
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // <
|
||||||
|
{ 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, // =
|
||||||
|
{ 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 }, // >
|
||||||
|
{ 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 }, // ?
|
||||||
|
{ 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E }, // @
|
||||||
|
{ 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C }, // A
|
||||||
|
{ 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B
|
||||||
|
{ 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C
|
||||||
|
{ 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D
|
||||||
|
{ 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E
|
||||||
|
{ 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F
|
||||||
|
{ 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G
|
||||||
|
{ 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H
|
||||||
|
{ 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I
|
||||||
|
{ 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J
|
||||||
|
{ 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K
|
||||||
|
{ 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L
|
||||||
|
{ 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M
|
||||||
|
{ 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N
|
||||||
|
{ 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O
|
||||||
|
{ 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P
|
||||||
|
{ 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q
|
||||||
|
{ 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R
|
||||||
|
{ 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 }, // S
|
||||||
|
{ 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T
|
||||||
|
{ 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U
|
||||||
|
{ 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V
|
||||||
|
{ 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W
|
||||||
|
{ 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 }, // X
|
||||||
|
{ 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y
|
||||||
|
{ 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z
|
||||||
|
{ 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [
|
||||||
|
{ 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 }, /* \ */
|
||||||
|
{ 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ]
|
||||||
|
{ 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^
|
||||||
|
{ 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 }, // _
|
||||||
|
{ 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 }, // '
|
||||||
|
{ 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 }, // a
|
||||||
|
{ 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b
|
||||||
|
{ 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 }, // c
|
||||||
|
{ 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F }, // d
|
||||||
|
{ 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 }, // e
|
||||||
|
{ 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f
|
||||||
|
{ 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g
|
||||||
|
{ 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h
|
||||||
|
{ 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i
|
||||||
|
{ 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 }, // j
|
||||||
|
{ 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k
|
||||||
|
{ 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l
|
||||||
|
{ 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m
|
||||||
|
{ 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n
|
||||||
|
{ 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 }, // o
|
||||||
|
{ 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 }, // p
|
||||||
|
{ 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC }, // q
|
||||||
|
{ 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r
|
||||||
|
{ 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 }, // s
|
||||||
|
{ 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t
|
||||||
|
{ 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u
|
||||||
|
{ 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v
|
||||||
|
{ 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w
|
||||||
|
{ 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 }, // x
|
||||||
|
{ 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C }, // y
|
||||||
|
{ 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z
|
||||||
|
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x14 }, // horiz lines
|
||||||
|
};
|
||||||
|
|
||||||
|
/****************************************8*16的点阵************************************/
|
||||||
|
// 取模方式:纵向8点下高位
|
||||||
|
// 采用一维数组,每个字符对应16个数组元素
|
||||||
|
// 每16个数组元素的前8个表示字符的上半部分(8*8点阵),后8个表示字符的下半部分(8*8点阵),一共8列16行
|
||||||
|
static const unsigned char F8X16[]=
|
||||||
|
{
|
||||||
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//空格 0
|
||||||
|
0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x30,0x00,0x00,0x00,//! 1
|
||||||
|
0x00,0x10,0x0C,0x06,0x10,0x0C,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//" 2
|
||||||
|
0x40,0xC0,0x78,0x40,0xC0,0x78,0x40,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x04,0x00,//# 3
|
||||||
|
0x00,0x70,0x88,0xFC,0x08,0x30,0x00,0x00,0x00,0x18,0x20,0xFF,0x21,0x1E,0x00,0x00,//$ 4
|
||||||
|
0xF0,0x08,0xF0,0x00,0xE0,0x18,0x00,0x00,0x00,0x21,0x1C,0x03,0x1E,0x21,0x1E,0x00,//% 5
|
||||||
|
0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x24,0x19,0x27,0x21,0x10,//& 6
|
||||||
|
0x10,0x16,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//' 7
|
||||||
|
0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00,//( 8
|
||||||
|
0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00,//) 9
|
||||||
|
0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00,//* 10
|
||||||
|
0x00,0x00,0x00,0xF0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x1F,0x01,0x01,0x01,0x00,//+ 11
|
||||||
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xB0,0x70,0x00,0x00,0x00,0x00,0x00,//, 12
|
||||||
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,//- 13
|
||||||
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00,//. 14
|
||||||
|
0x00,0x00,0x00,0x00,0x80,0x60,0x18,0x04,0x00,0x60,0x18,0x06,0x01,0x00,0x00,0x00,/// 15
|
||||||
|
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00,//0 16
|
||||||
|
0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//1 17
|
||||||
|
0x00,0x70,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00,//2 18
|
||||||
|
0x00,0x30,0x08,0x88,0x88,0x48,0x30,0x00,0x00,0x18,0x20,0x20,0x20,0x11,0x0E,0x00,//3 19
|
||||||
|
0x00,0x00,0xC0,0x20,0x10,0xF8,0x00,0x00,0x00,0x07,0x04,0x24,0x24,0x3F,0x24,0x00,//4 20
|
||||||
|
0x00,0xF8,0x08,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x21,0x20,0x20,0x11,0x0E,0x00,//5 21
|
||||||
|
0x00,0xE0,0x10,0x88,0x88,0x18,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x11,0x0E,0x00,//6 22
|
||||||
|
0x00,0x38,0x08,0x08,0xC8,0x38,0x08,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00,0x00,//7 23
|
||||||
|
0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00,//8 24
|
||||||
|
0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x00,0x31,0x22,0x22,0x11,0x0F,0x00,//9 25
|
||||||
|
0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,//: 26
|
||||||
|
0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x60,0x00,0x00,0x00,0x00,//; 27
|
||||||
|
0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00,//< 28
|
||||||
|
0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x04,0x04,0x04,0x04,0x04,0x04,0x04,0x00,//= 29
|
||||||
|
0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00,//> 30
|
||||||
|
0x00,0x70,0x48,0x08,0x08,0x08,0xF0,0x00,0x00,0x00,0x00,0x30,0x36,0x01,0x00,0x00,//? 31
|
||||||
|
0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x24,0x23,0x14,0x0B,0x00,//@ 32
|
||||||
|
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 33
|
||||||
|
0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 34
|
||||||
|
0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 35
|
||||||
|
0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 36
|
||||||
|
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 37
|
||||||
|
0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 38
|
||||||
|
0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 39
|
||||||
|
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H 40
|
||||||
|
0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 41
|
||||||
|
0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 42
|
||||||
|
0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 43
|
||||||
|
0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 44
|
||||||
|
0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 45
|
||||||
|
0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 46
|
||||||
|
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 47
|
||||||
|
0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 48
|
||||||
|
0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 49
|
||||||
|
0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 50
|
||||||
|
0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 51
|
||||||
|
0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 52
|
||||||
|
0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 53
|
||||||
|
0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 54
|
||||||
|
0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 55
|
||||||
|
0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 56
|
||||||
|
0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 57
|
||||||
|
0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 58
|
||||||
|
0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00,//[ 59
|
||||||
|
0x00,0x0C,0x30,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00,//\ 60
|
||||||
|
0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00,//] 61
|
||||||
|
0x00,0x00,0x04,0x02,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//^ 62
|
||||||
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,//_ 63
|
||||||
|
0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//` 64
|
||||||
|
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x19,0x24,0x22,0x22,0x22,0x3F,0x20,//a 65
|
||||||
|
0x08,0xF8,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00,//b 66
|
||||||
|
0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00,//c 67
|
||||||
|
0x00,0x00,0x00,0x80,0x80,0x88,0xF8,0x00,0x00,0x0E,0x11,0x20,0x20,0x10,0x3F,0x20,//d 68
|
||||||
|
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x22,0x22,0x22,0x22,0x13,0x00,//e 69
|
||||||
|
0x00,0x80,0x80,0xF0,0x88,0x88,0x88,0x18,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//f 70
|
||||||
|
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00,//g 71
|
||||||
|
0x08,0xF8,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//h 72
|
||||||
|
0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//i 73
|
||||||
|
0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,//j 74
|
||||||
|
0x08,0xF8,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x02,0x2D,0x30,0x20,0x00,//k 75
|
||||||
|
0x00,0x08,0x08,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//l 76
|
||||||
|
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F,//m 77
|
||||||
|
0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20,//n 78
|
||||||
|
0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//o 79
|
||||||
|
0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0xA1,0x20,0x20,0x11,0x0E,0x00,//p 80
|
||||||
|
0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0xA0,0xFF,0x80,//q 81
|
||||||
|
0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00,//r 82
|
||||||
|
0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00,//s 83
|
||||||
|
0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x00,0x00,//t 84
|
||||||
|
0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20,//u 85
|
||||||
|
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x01,0x0E,0x30,0x08,0x06,0x01,0x00,//v 86
|
||||||
|
0x80,0x80,0x00,0x80,0x00,0x80,0x80,0x80,0x0F,0x30,0x0C,0x03,0x0C,0x30,0x0F,0x00,//w 87
|
||||||
|
0x00,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x31,0x2E,0x0E,0x31,0x20,0x00,//x 88
|
||||||
|
0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x80,0x81,0x8E,0x70,0x18,0x06,0x01,0x00,//y 89
|
||||||
|
0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00,//z 90
|
||||||
|
0x00,0x00,0x00,0x00,0x80,0x7C,0x02,0x02,0x00,0x00,0x00,0x00,0x00,0x3F,0x40,0x40,//{ 91
|
||||||
|
0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,//| 92
|
||||||
|
0x00,0x02,0x02,0x7C,0x80,0x00,0x00,0x00,0x00,0x40,0x40,0x3F,0x00,0x00,0x00,0x00,//} 93
|
||||||
|
0x00,0x06,0x01,0x01,0x02,0x02,0x04,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,//~ 94
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
294
oled_ssd1306.c
Normal file
294
oled_ssd1306.c
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
// OLED显示屏简化版驱动源文件
|
||||||
|
|
||||||
|
#include <stdio.h> // 标准输入输出
|
||||||
|
#include <stddef.h> // 标准类型定义
|
||||||
|
|
||||||
|
#include "iot_gpio.h" // OpenHarmony HAL:IoT硬件设备操作接口-GPIO
|
||||||
|
#include "iot_i2c.h" // OpenHarmony HAL:IoT硬件设备操作接口-I2C
|
||||||
|
#include "iot_errno.h" // OpenHarmony HAL:IoT硬件设备操作接口-错误代码定义
|
||||||
|
#include "hi_io.h" // 海思 Pegasus SDK:IoT硬件设备操作接口-IO
|
||||||
|
|
||||||
|
// 字库头文件
|
||||||
|
#include "oled_fonts.h"
|
||||||
|
|
||||||
|
// OLED显示屏简化版驱动接口文件
|
||||||
|
#include "oled_ssd1306.h"
|
||||||
|
|
||||||
|
// 定义一个宏,用于计算数组的长度
|
||||||
|
#define ARRAY_SIZE(a) sizeof(a) / sizeof(a[0])
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识I2C0
|
||||||
|
#define OLED_I2C_IDX 0
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识I2C0的波特率(传输速率)
|
||||||
|
#define OLED_I2C_BAUDRATE (400 * 1000) // 400KHz
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识OLED的宽度
|
||||||
|
#define OLED_WIDTH (128)
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识SSD1306显示屏驱动芯片的设备地址
|
||||||
|
#define OLED_I2C_ADDR 0x78
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识写命令操作
|
||||||
|
#define OLED_I2C_CMD 0x00 // 0000 0000 写命令
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识写数据操作
|
||||||
|
#define OLED_I2C_DATA 0x40 // 0100 0000(0x40) 写数据
|
||||||
|
|
||||||
|
// 定义一个宏,用于标识100ms的延时
|
||||||
|
#define DELAY_100_MS (100 * 1000)
|
||||||
|
|
||||||
|
// 定义一个结构体,表示要发送或接收的数据
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
// 要发送的数据的指针
|
||||||
|
unsigned char *sendBuf;
|
||||||
|
// 要发送的数据长度
|
||||||
|
unsigned int sendLen;
|
||||||
|
// 要接收的数据的指针
|
||||||
|
unsigned char *receiveBuf;
|
||||||
|
// 要接收的数据长度
|
||||||
|
unsigned int receiveLen;
|
||||||
|
} IotI2cData;
|
||||||
|
|
||||||
|
/// @brief 向OLED写一个字节
|
||||||
|
/// @param regAddr 写入命令还是数据 OLED_I2C_CMD / OLED_I2C_DATA
|
||||||
|
/// @param byte 写入的内容
|
||||||
|
/// @retval 成功返回IOT_SUCCESS,失败返回IOT_FAILURE
|
||||||
|
static uint32_t I2cWiteByte(uint8_t regAddr, uint8_t byte)
|
||||||
|
{
|
||||||
|
// 定义字节流
|
||||||
|
uint8_t buffer[] = {regAddr, byte};
|
||||||
|
IotI2cData i2cData = {0};
|
||||||
|
i2cData.sendBuf = buffer;
|
||||||
|
i2cData.sendLen = sizeof(buffer) / sizeof(buffer[0]);
|
||||||
|
|
||||||
|
// 发送字节流
|
||||||
|
return IoTI2cWrite(OLED_I2C_IDX, OLED_I2C_ADDR, i2cData.sendBuf, i2cData.sendLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 向OLED写一个命令字节
|
||||||
|
/// @param cmd 写入的命令字节
|
||||||
|
/// @return 成功返回IOT_SUCCESS,失败返回IOT_FAILURE
|
||||||
|
static uint32_t WriteCmd(uint8_t cmd)
|
||||||
|
{
|
||||||
|
return I2cWiteByte(OLED_I2C_CMD, cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 向OLED写一个数据字节
|
||||||
|
/// @param cmd 写入的数据字节
|
||||||
|
/// @return 成功返回IOT_SUCCESS,失败返回IOT_FAILURE
|
||||||
|
uint32_t WriteData(uint8_t data)
|
||||||
|
{
|
||||||
|
return I2cWiteByte(OLED_I2C_DATA, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 初始化SSD1306显示屏驱动芯片
|
||||||
|
uint32_t OledInit(void)
|
||||||
|
{
|
||||||
|
// 构造初始化代码
|
||||||
|
static const uint8_t initCmds[] = {
|
||||||
|
0xAE, // 显示关闭
|
||||||
|
0x00, // 页寻址模式时,设置列地址的低4位为0000
|
||||||
|
0x10, // 页寻址模式时,设置列地址的高4位为0000
|
||||||
|
0x40, // 设置起始行地址为第0行
|
||||||
|
0xB0, // 页寻址模式时,设置页面起始地址为PAGE0
|
||||||
|
0x81, // 设置对比度
|
||||||
|
0xFF, // 对比度数值
|
||||||
|
0xA1, // set segment remap
|
||||||
|
0xA6, // 设置正常显示。0对应像素熄灭,1对应像素亮起
|
||||||
|
0xA8, // --set multiplex ratio(1 to 64)
|
||||||
|
0x3F, // --1/32 duty
|
||||||
|
0xC8, // Com scan direction
|
||||||
|
0xD3, // -set display offset
|
||||||
|
0x00, //
|
||||||
|
0xD5, // set osc division
|
||||||
|
0x80, //
|
||||||
|
0xD8, // set area color mode off
|
||||||
|
0x05, //
|
||||||
|
0xD9, // Set Pre-Charge Period
|
||||||
|
0xF1, //
|
||||||
|
0xDA, // set com pin configuartion
|
||||||
|
0x12, //
|
||||||
|
0xDB, // set Vcomh
|
||||||
|
0x30, //
|
||||||
|
0x8D, // set charge pump enable
|
||||||
|
0x14, //
|
||||||
|
0xAF, // 显示开启
|
||||||
|
};
|
||||||
|
|
||||||
|
// 初始化GPIO-13
|
||||||
|
IoTGpioInit(HI_IO_NAME_GPIO_13);
|
||||||
|
// 设置GPIO-13引脚功能为I2C0_SDA
|
||||||
|
hi_io_set_func(HI_IO_NAME_GPIO_13, HI_IO_FUNC_GPIO_13_I2C0_SDA);
|
||||||
|
// 初始化GPIO-14
|
||||||
|
IoTGpioInit(HI_IO_NAME_GPIO_14);
|
||||||
|
// 设置GPIO-14引脚功能为I2C0_SCL
|
||||||
|
hi_io_set_func(HI_IO_NAME_GPIO_14, HI_IO_FUNC_GPIO_14_I2C0_SCL);
|
||||||
|
|
||||||
|
// 用指定的波特速率初始化I2C0
|
||||||
|
IoTI2cInit(OLED_I2C_IDX, OLED_I2C_BAUDRATE);
|
||||||
|
|
||||||
|
// 发送初始化代码,初始化SSD1306显示屏驱动芯片
|
||||||
|
for (size_t i = 0; i < ARRAY_SIZE(initCmds); i++)
|
||||||
|
{
|
||||||
|
// 发送一个命令字节
|
||||||
|
uint32_t status = WriteCmd(initCmds[i]);
|
||||||
|
if (status != IOT_SUCCESS)
|
||||||
|
{
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OLED初始化完成,返回成功
|
||||||
|
return IOT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 设置显示位置
|
||||||
|
/// @param x x坐标,1像素为单位
|
||||||
|
/// @param y y坐标,8像素为单位。即页面起始地址
|
||||||
|
/// @return 无
|
||||||
|
void OledSetPosition(uint8_t x, uint8_t y)
|
||||||
|
{
|
||||||
|
//设置页面起始地址
|
||||||
|
WriteCmd(0xb0 + y);
|
||||||
|
|
||||||
|
// 列:0~127
|
||||||
|
// 第0列:0x00列,二进制00000000。低地址0000,即0x00。高地址0000(需要|0x10),0000|0x10=0x10。
|
||||||
|
// 第127列:0x7f列,二进制01111111。低地址1111,即0x0F。高地址0111(需要|0x10),0111|0x10=0x17。
|
||||||
|
|
||||||
|
// 设置显示位置:列地址的低4位
|
||||||
|
// 直接取出列地址低4位作为命令代码的低4位,命令代码的高4位为0000
|
||||||
|
WriteCmd(x & 0x0f);
|
||||||
|
|
||||||
|
// 设置显示位置:列地址的高4位
|
||||||
|
// 取出列地址高4位作为命令代码的低4位,命令代码的高4位必须为0001
|
||||||
|
// 实际编程时,列地址的高4位和0x10(二进制00010000)进行按位或即得到命令代码
|
||||||
|
WriteCmd(((x & 0xf0) >> 4) | 0x10);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 全屏填充
|
||||||
|
/// @param fillData 填充的数据,1字节
|
||||||
|
/// @return 无
|
||||||
|
void OledFillScreen(uint8_t fillData)
|
||||||
|
{
|
||||||
|
// 相关变量,用于遍历page和列
|
||||||
|
uint8_t m = 0;
|
||||||
|
uint8_t n = 0;
|
||||||
|
|
||||||
|
// 写入所有页的数据
|
||||||
|
for (m = 0; m < 8; m++)
|
||||||
|
{
|
||||||
|
//设置页地址:0~7
|
||||||
|
WriteCmd(0xb0 + m);
|
||||||
|
|
||||||
|
// 设置显示位置为第0列
|
||||||
|
WriteCmd(0x00); //设置显示位置:列低地址(0000)
|
||||||
|
WriteCmd(0x10); //设置显示位置:列高地址(0000)
|
||||||
|
|
||||||
|
// 写入128列数据
|
||||||
|
// 在一个页中,数据按列写入,一次一列,对应发送过来的1字节数据
|
||||||
|
for (n = 0; n < 128; n++)
|
||||||
|
{
|
||||||
|
// 写入一个字节数据
|
||||||
|
WriteData(fillData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 清屏函数
|
||||||
|
/// @return 无
|
||||||
|
void OledClearScreen(void)
|
||||||
|
{
|
||||||
|
OledFillScreen(0x00); // 用0x00填充整个屏幕,实现清屏
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// @brief 显示一个字符
|
||||||
|
/// @param x: x坐标,1像素为单位
|
||||||
|
/// @param y: y坐标,8像素为单位
|
||||||
|
/// @param ch: 要显示的字符
|
||||||
|
/// @param font: 字库
|
||||||
|
void OledShowChar(uint8_t x, uint8_t y, uint8_t ch, Font font)
|
||||||
|
{
|
||||||
|
// 数组下标
|
||||||
|
uint8_t c = 0;
|
||||||
|
|
||||||
|
// 循环控制
|
||||||
|
uint8_t i = 0;
|
||||||
|
|
||||||
|
// 得到数组下标
|
||||||
|
// 空格的ASCII码32,在字库中的下标是0。字库中的字符-空格即相应的数组下标
|
||||||
|
c = ch - ' ';
|
||||||
|
|
||||||
|
// 显示字符
|
||||||
|
if (font == FONT8x16) // 8*16的点阵,一个page放不下
|
||||||
|
{
|
||||||
|
// 显示字符的上半部分
|
||||||
|
// 设置显示位置
|
||||||
|
OledSetPosition(x, y);
|
||||||
|
// 逐个字节写入(16个数组元素的前8个)
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
WriteData(F8X16[c * 16 + i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 显示字符的下半部分
|
||||||
|
// 设置显示位置为下一个PAGE
|
||||||
|
OledSetPosition(x, y + 1);
|
||||||
|
// 逐个字节写入(16个数组元素的后8个)
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
WriteData(F8X16[c * 16 + 8 + i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else // 6*8的点阵,在一个page中
|
||||||
|
{
|
||||||
|
// 设置显示位置
|
||||||
|
OledSetPosition(x, y);
|
||||||
|
// 逐个字节写入(数组第二维的6个数组元素)
|
||||||
|
for (i = 0; i < 6; i++)
|
||||||
|
{
|
||||||
|
WriteData(F6x8[c][i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief 显示一个字符串
|
||||||
|
/// @param x: x坐标,1像素为单位
|
||||||
|
/// @param y: y坐标,8像素为单位
|
||||||
|
/// @param str: 要显示的字符串
|
||||||
|
/// @param font: 字库
|
||||||
|
void OledShowString(uint8_t x, uint8_t y, const char *str, Font font)
|
||||||
|
{
|
||||||
|
// 字符数组(字符串)下标
|
||||||
|
uint8_t j = 0;
|
||||||
|
|
||||||
|
// 检查字符串是否为空
|
||||||
|
if (str == NULL)
|
||||||
|
{
|
||||||
|
printf("param is NULL,Please check!!!\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历字符串,显示每个字符
|
||||||
|
while (str[j])
|
||||||
|
{
|
||||||
|
// 显示一个字符
|
||||||
|
OledShowChar(x, y, str[j], font);
|
||||||
|
|
||||||
|
// 设置字符间距
|
||||||
|
x += 8;
|
||||||
|
|
||||||
|
// 如果下一个要显示的字符超出了OLED显示的范围,则换行
|
||||||
|
if (x > 120)
|
||||||
|
{
|
||||||
|
x = 0;
|
||||||
|
y += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一个字符
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
oled_ssd1306.h
Normal file
32
oled_ssd1306.h
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// OLED显示屏简化版驱动接口文件
|
||||||
|
|
||||||
|
// 定义条件编译宏,防止头文件的重复包含和编译
|
||||||
|
#ifndef OLED_SSD1306_H
|
||||||
|
#define OLED_SSD1306_H
|
||||||
|
|
||||||
|
#include <stdint.h> // 定义了几种扩展的整数类型和宏
|
||||||
|
|
||||||
|
// 声明接口函数
|
||||||
|
|
||||||
|
uint32_t OledInit(void);
|
||||||
|
void OledSetPosition(uint8_t x, uint8_t y);
|
||||||
|
void OledFillScreen(uint8_t fillData);
|
||||||
|
uint32_t WriteData(uint8_t data);
|
||||||
|
|
||||||
|
// 清屏函数
|
||||||
|
void OledClearScreen(void);
|
||||||
|
|
||||||
|
// 定义字库类型
|
||||||
|
enum Font {
|
||||||
|
FONT6x8 = 1,
|
||||||
|
FONT8x16
|
||||||
|
};
|
||||||
|
typedef enum Font Font;
|
||||||
|
|
||||||
|
// 声明接口函数
|
||||||
|
|
||||||
|
void OledShowChar(uint8_t x, uint8_t y, uint8_t ch, Font font);
|
||||||
|
void OledShowString(uint8_t x, uint8_t y, const char* str, Font font);
|
||||||
|
|
||||||
|
// 条件编译结束
|
||||||
|
#endif // OLED_SSD1306_H
|
||||||
69
robot_sg90.c
Normal file
69
robot_sg90.c
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <memory.h>
|
||||||
|
|
||||||
|
#include "ohos_init.h"
|
||||||
|
#include "cmsis_os2.h"
|
||||||
|
#include "iot_gpio.h"
|
||||||
|
#include "hi_io.h"
|
||||||
|
#include "hi_time.h"
|
||||||
|
|
||||||
|
//查阅机器人板原理图可知,SG90舵机通过GPIO2与3861连接
|
||||||
|
//SG90舵机的控制需要MCU产生一个周期为20ms的脉冲信号,以0.5ms到2.5ms的高电平来控制舵机转动的角度
|
||||||
|
#define GPIO2 2
|
||||||
|
|
||||||
|
//输出20000微秒的脉冲信号(x微秒高电平,20000-x微秒低电平)
|
||||||
|
void set_angle( unsigned int duty) {
|
||||||
|
IoTGpioSetDir(GPIO2, IOT_GPIO_DIR_OUT);//设置GPIO2为输出模式
|
||||||
|
|
||||||
|
//GPIO2输出x微秒高电平
|
||||||
|
IoTGpioSetOutputVal(GPIO2, IOT_GPIO_VALUE1);
|
||||||
|
hi_udelay(duty);
|
||||||
|
|
||||||
|
//GPIO2输出20000-x微秒低电平
|
||||||
|
IoTGpioSetOutputVal(GPIO2, IOT_GPIO_VALUE0);
|
||||||
|
hi_udelay(20000 - duty);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Steering gear turn left (counter-clockwise 90 degrees)
|
||||||
|
1、依据角度与脉冲的关系,设置高电平时间为1000微秒
|
||||||
|
2、发送20次脉冲信号,控制舵机逆时针旋转90度
|
||||||
|
*/
|
||||||
|
void engine_turn_left(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
set_angle(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Steering gear turn right (clockwise 90 degrees)
|
||||||
|
1、依据角度与脉冲的关系,设置高电平时间为2000微秒
|
||||||
|
2、发送20次脉冲信号,控制舵机顺时针旋转90度
|
||||||
|
*/
|
||||||
|
void engine_turn_right(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
set_angle(2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*Steering gear return to middle
|
||||||
|
1、依据角度与脉冲的关系,设置高电平时间为1500微秒
|
||||||
|
2、发送20次脉冲信号,控制舵机居中
|
||||||
|
*/
|
||||||
|
void regress_middle(void)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 20; i++) {
|
||||||
|
set_angle(1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顺时针旋转90度
|
||||||
|
void servo_rotate_clockwise_90(void) {
|
||||||
|
engine_turn_right();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逆时针旋转90度
|
||||||
|
void servo_rotate_counter_clockwise_90(void) {
|
||||||
|
engine_turn_left();
|
||||||
|
}
|
||||||
13
robot_sg90.h
Normal file
13
robot_sg90.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef ROBOT_SG90_H
|
||||||
|
#define ROBOT_SG90_H
|
||||||
|
|
||||||
|
void set_angle(int angle);
|
||||||
|
void engine_turn_left(void);
|
||||||
|
void engine_turn_right(void);
|
||||||
|
void regress_middle(void);
|
||||||
|
|
||||||
|
// 新增的90度精确旋转函数
|
||||||
|
void servo_rotate_clockwise_90(void);
|
||||||
|
void servo_rotate_counter_clockwise_90(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
53
simple_client.py
Normal file
53
simple_client.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
简单的UDP客户端程序
|
||||||
|
向Hi3861设备发送JSON命令
|
||||||
|
"""
|
||||||
|
|
||||||
|
import socket
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
|
||||||
|
def send_command():
|
||||||
|
"""发送命令到Hi3861设备"""
|
||||||
|
# 目标设备信息
|
||||||
|
target_ip = "192.168.43.12"
|
||||||
|
target_port = 8081
|
||||||
|
|
||||||
|
# 创建JSON命令
|
||||||
|
command = {
|
||||||
|
"cmd": 1, # 测试命令4:只显示字符串,舵机不动
|
||||||
|
"text": "沪AAAAAA 通行 2sec"
|
||||||
|
}
|
||||||
|
|
||||||
|
json_command = json.dumps(command, ensure_ascii=False)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 创建UDP socket
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
|
||||||
|
# 发送命令
|
||||||
|
print(f"正在向 {target_ip}:{target_port} 发送命令...")
|
||||||
|
print(f"命令内容: {json_command}")
|
||||||
|
|
||||||
|
sock.sendto(json_command.encode('utf-8'), (target_ip, target_port))
|
||||||
|
|
||||||
|
print("命令发送成功!")
|
||||||
|
print("设备将执行以下操作:")
|
||||||
|
print("1. 顺时针旋转舵机90度")
|
||||||
|
print("2. 在OLED屏幕上显示:沪AAAAAA")
|
||||||
|
print("3. 等待10秒")
|
||||||
|
print("4. 逆时针旋转舵机90度")
|
||||||
|
print("5. 清空OLED屏幕")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"发送命令失败: {e}")
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Hi3861 简单客户端程序")
|
||||||
|
print("=" * 30)
|
||||||
|
send_command()
|
||||||
|
print("程序结束")
|
||||||
56
test_json.c
Normal file
56
test_json.c
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "json_parser.h"
|
||||||
|
|
||||||
|
// 测试JSON解析功能
|
||||||
|
void test_json_parsing() {
|
||||||
|
printf("=== JSON解析测试 ===\n");
|
||||||
|
|
||||||
|
// 测试命令1:旋转+显示+清屏
|
||||||
|
char test1[] = "{\"cmd\":1,\"text\":\"Hello World\"}";
|
||||||
|
JsonCommand cmd1;
|
||||||
|
if (ParseJsonCommand(test1, &cmd1) == 0) {
|
||||||
|
printf("测试1成功: cmd=%d, text=%s\n", cmd1.cmd, cmd1.text);
|
||||||
|
} else {
|
||||||
|
printf("测试1失败\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试命令2:顺时针旋转
|
||||||
|
char test2[] = "{\"cmd\":2,\"text\":\"\"}";
|
||||||
|
JsonCommand cmd2;
|
||||||
|
if (ParseJsonCommand(test2, &cmd2) == 0) {
|
||||||
|
printf("测试2成功: cmd=%d, text=%s\n", cmd2.cmd, cmd2.text);
|
||||||
|
} else {
|
||||||
|
printf("测试2失败\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试命令3:逆时针旋转
|
||||||
|
char test3[] = "{\"cmd\":3,\"text\":\"ignored\"}";
|
||||||
|
JsonCommand cmd3;
|
||||||
|
if (ParseJsonCommand(test3, &cmd3) == 0) {
|
||||||
|
printf("测试3成功: cmd=%d, text=%s\n", cmd3.cmd, cmd3.text);
|
||||||
|
} else {
|
||||||
|
printf("测试3失败\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试IP消息创建
|
||||||
|
printf("\n=== IP消息创建测试 ===\n");
|
||||||
|
char ip_msg[128];
|
||||||
|
CreateIpMessage("192.168.1.100", ip_msg, sizeof(ip_msg));
|
||||||
|
printf("IP消息: %s\n", ip_msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 测试舵机控制逻辑
|
||||||
|
void test_servo_commands() {
|
||||||
|
printf("\n=== 舵机命令测试 ===\n");
|
||||||
|
|
||||||
|
printf("命令1: 顺时针90°+显示+10秒后逆时针90°+清屏\n");
|
||||||
|
printf("命令2: 顺时针90°\n");
|
||||||
|
printf("命令3: 逆时针90°\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
test_json_parsing();
|
||||||
|
test_servo_commands();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
242
udp_client_test.c
Normal file
242
udp_client_test.c
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
#include <stdio.h> // 标准输入输出
|
||||||
|
#include <unistd.h> // POSIX标准接口
|
||||||
|
#include <errno.h> // 错误码
|
||||||
|
#include <string.h> // 字符串处理(操作字符数组)
|
||||||
|
|
||||||
|
#include "lwip/sockets.h" // lwIP TCP/IP协议栈:Socket API
|
||||||
|
#include "ohos_init.h" // 用于初始化服务(services)和功能(features)
|
||||||
|
#include "cmsis_os2.h" // CMSIS-RTOS API V2
|
||||||
|
#include "oled_ssd1306.h"
|
||||||
|
#include "json_parser.h" // JSON解析器
|
||||||
|
#include "wifi_connecter.h" // WiFi连接器
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extern int control_flag ;
|
||||||
|
|
||||||
|
// 要发送的数据
|
||||||
|
static char request[] = "connecting";
|
||||||
|
|
||||||
|
// 要接收的数据
|
||||||
|
char response[128] = "";
|
||||||
|
|
||||||
|
// 全局变量存储解析后的命令
|
||||||
|
JsonCommand g_current_command = {0};
|
||||||
|
|
||||||
|
// 发送IP地址到服务器
|
||||||
|
void SendIpToServer(const char *host, unsigned short port) {
|
||||||
|
char ip_buffer[32] = {0};
|
||||||
|
char message_buffer[128] = {0};
|
||||||
|
|
||||||
|
// 获取本机IP地址
|
||||||
|
if (GetLocalIpAddress(ip_buffer, sizeof(ip_buffer)) != 0) {
|
||||||
|
printf("Failed to get local IP address\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建IP消息
|
||||||
|
if (CreateIpMessage(message_buffer, sizeof(message_buffer), ip_buffer) != 0) {
|
||||||
|
printf("Failed to create IP message\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建UDP socket
|
||||||
|
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
printf("Failed to create socket\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in toAddr = {0};
|
||||||
|
toAddr.sin_family = AF_INET;
|
||||||
|
toAddr.sin_port = htons(port);
|
||||||
|
|
||||||
|
if (inet_pton(AF_INET, host, &toAddr.sin_addr) <= 0) {
|
||||||
|
printf("inet_pton failed!\r\n");
|
||||||
|
lwip_close(sockfd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送IP地址消息
|
||||||
|
ssize_t retval = sendto(sockfd, message_buffer, strlen(message_buffer), 0,
|
||||||
|
(struct sockaddr *)&toAddr, sizeof(toAddr));
|
||||||
|
|
||||||
|
if (retval < 0) {
|
||||||
|
printf("Failed to send IP message!\r\n");
|
||||||
|
} else {
|
||||||
|
printf("IP message sent: %s\r\n", message_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
lwip_close(sockfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @brief UDP客户端测试函数
|
||||||
|
/// @param host UDP服务端IP地址
|
||||||
|
/// @param port UDP服务端端口
|
||||||
|
void UdpClientTest(const char *host, unsigned short port)
|
||||||
|
{
|
||||||
|
// 用于接收Socket API接口返回值
|
||||||
|
ssize_t retval = 0;
|
||||||
|
// 创建一个UDP Socket,返回值为文件描述符
|
||||||
|
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (sockfd < 0) {
|
||||||
|
printf("创建socket失败!\r\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用于设置服务端的地址信息
|
||||||
|
struct sockaddr_in toAddr = {0};
|
||||||
|
// 用于设置本地绑定的地址信息
|
||||||
|
struct sockaddr_in localAddr = {0};
|
||||||
|
|
||||||
|
// 使用IPv4协议
|
||||||
|
toAddr.sin_family = AF_INET;
|
||||||
|
localAddr.sin_family = AF_INET;
|
||||||
|
|
||||||
|
// 端口号,从主机字节序转为网络字节序
|
||||||
|
toAddr.sin_port = htons(port);
|
||||||
|
localAddr.sin_port = htons(port); // 绑定到相同端口接收数据
|
||||||
|
localAddr.sin_addr.s_addr = INADDR_ANY; // 绑定到所有可用的网络接口
|
||||||
|
|
||||||
|
// 将服务端IP地址从"点分十进制"字符串,转化为标准格式(32位整数)
|
||||||
|
if (inet_pton(AF_INET, host, &toAddr.sin_addr) <= 0)
|
||||||
|
{
|
||||||
|
// 转化失败
|
||||||
|
printf("inet_pton failed!\r\n"); // 输出日志
|
||||||
|
lwip_close(sockfd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定本地端口,这样才能接收数据
|
||||||
|
printf("绑定本地端口 %d...\r\n", port);
|
||||||
|
if (bind(sockfd, (struct sockaddr *)&localAddr, sizeof(localAddr)) < 0) {
|
||||||
|
printf("绑定端口失败!错误: %d\r\n", errno);
|
||||||
|
lwip_close(sockfd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
printf("端口绑定成功!\r\n");
|
||||||
|
|
||||||
|
// 发送设备IP地址到服务器
|
||||||
|
SendIpToServer(sockfd, &toAddr);
|
||||||
|
|
||||||
|
// 发送数据
|
||||||
|
// UDP socket是 "无连接的",因此每次发送都必须先指定目标主机和端口,主机可以是多播地址
|
||||||
|
// 发送数据的时候,使用本地随机端口N
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// s:socket文件描述符
|
||||||
|
// dataptr:要发送的数据
|
||||||
|
// size:要发送的数据的长度,最大65332字节
|
||||||
|
// flags:消息传输标志位
|
||||||
|
// to:目标的地址信息
|
||||||
|
// tolen:目标的地址信息长度
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// 发送的字节数,如果出错,返回-1
|
||||||
|
printf("发送连接消息到服务器...\r\n");
|
||||||
|
retval = sendto(sockfd, request, sizeof(request), 0, (struct sockaddr *)&toAddr, sizeof(toAddr));
|
||||||
|
|
||||||
|
// 检查接口返回值,小于0表示发送失败
|
||||||
|
if (retval < 0)
|
||||||
|
{
|
||||||
|
// 发送失败
|
||||||
|
printf("发送消息失败!错误: %d\r\n", errno); // 输出日志
|
||||||
|
lwip_close(sockfd);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送成功
|
||||||
|
// 输出日志
|
||||||
|
printf("发送UDP消息成功: {%s} %ld 字节\r\n", request, retval);
|
||||||
|
|
||||||
|
// 显示等待接收状态
|
||||||
|
printf("开始监听UDP消息,等待来自 %s 的数据...\r\n", host);
|
||||||
|
|
||||||
|
// 用于记录发送方的地址信息(IP地址和端口号)
|
||||||
|
struct sockaddr_in fromAddr = {0};
|
||||||
|
|
||||||
|
// 用于记录发送方的地址信息长度
|
||||||
|
socklen_t fromLen = sizeof(fromAddr);
|
||||||
|
|
||||||
|
// 在本地随机端口N上面接收数据
|
||||||
|
// UDP socket是 “无连接的”,因此每次接收时并不知道消息来自何处,通过fromAddr参数可以得到发送方的信息(主机、端口号)
|
||||||
|
// device\hisilicon\hispark_pegasus\sdk_liteos\third_party\lwip_sack\include\lwip\sockets.h -> lwip_recvfrom
|
||||||
|
//
|
||||||
|
// 参数:
|
||||||
|
// s:socket文件描述符
|
||||||
|
// buffer:接收数据的缓冲区的地址
|
||||||
|
// length:接收数据的缓冲区的长度
|
||||||
|
// flags:消息接收标志位
|
||||||
|
// address:发送方的地址信息
|
||||||
|
// address_len:发送方的地址信息长度
|
||||||
|
//
|
||||||
|
// 返回值:
|
||||||
|
// 接收的字节数,如果出错,返回-1
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// 清空接收缓冲区
|
||||||
|
memset(response, 0, sizeof(response));
|
||||||
|
|
||||||
|
retval = lwip_recvfrom(sockfd, response, sizeof(response) - 1, 0, (struct sockaddr *)&fromAddr, &fromLen);
|
||||||
|
|
||||||
|
// 检查接口返回值,小于0表示接收失败
|
||||||
|
if (retval <= 0)
|
||||||
|
{
|
||||||
|
// 接收失败,或者收到0长度的数据(忽略掉)
|
||||||
|
printf("recvfrom failed or abort, %ld, %d!\r\n", retval, errno); // 输出日志
|
||||||
|
continue; // 继续接收,不要关闭socket
|
||||||
|
}
|
||||||
|
|
||||||
|
// 接收成功
|
||||||
|
// 末尾添加字符串结束符'\0',以便后续的字符串操作
|
||||||
|
response[retval] = '\0';
|
||||||
|
|
||||||
|
// 输出日志 - 显示所有收到的消息
|
||||||
|
printf("=== 收到UDP消息 ===\r\n");
|
||||||
|
printf("消息内容: {%s}\r\n", response);
|
||||||
|
printf("消息长度: %ld 字节\r\n", retval);
|
||||||
|
printf("发送方IP: %s\r\n", inet_ntoa(fromAddr.sin_addr));
|
||||||
|
printf("发送方端口: %d\r\n", ntohs(fromAddr.sin_port));
|
||||||
|
printf("==================\r\n");
|
||||||
|
|
||||||
|
// 尝试解析JSON命令
|
||||||
|
JsonCommand temp_cmd = {0};
|
||||||
|
if (ParseJsonCommand(response, &temp_cmd) == 0) {
|
||||||
|
printf("JSON解析成功: cmd=%d, text=%s\r\n", temp_cmd.cmd, temp_cmd.text);
|
||||||
|
|
||||||
|
// 保存解析的命令
|
||||||
|
g_current_command = temp_cmd;
|
||||||
|
|
||||||
|
// 根据命令类型设置控制标志
|
||||||
|
if (temp_cmd.cmd >= 1 && temp_cmd.cmd <= 4) {
|
||||||
|
control_flag = temp_cmd.cmd;
|
||||||
|
printf("设置控制标志为: %d\r\n", control_flag);
|
||||||
|
} else {
|
||||||
|
printf("无效的命令类型: %d\r\n", temp_cmd.cmd);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printf("JSON解析失败,尝试旧格式命令\r\n");
|
||||||
|
|
||||||
|
// 兼容旧的字符串命令格式
|
||||||
|
if(strlen(response) > 10) {
|
||||||
|
control_flag = 2;
|
||||||
|
g_current_command.cmd = 0; // 标记为旧命令
|
||||||
|
printf("设置为复杂字符串命令,控制标志: %d\r\n", control_flag);
|
||||||
|
} else if(strcmp(response,"T:on") == 0) {
|
||||||
|
control_flag = 1;
|
||||||
|
g_current_command.cmd = 0; // 标记为旧命令
|
||||||
|
printf("设置为开启命令,控制标志: %d\r\n", control_flag);
|
||||||
|
} else if(strcmp(response,"T:off") == 0) {
|
||||||
|
control_flag = 0;
|
||||||
|
g_current_command.cmd = 0; // 标记为旧命令
|
||||||
|
printf("设置为关闭命令,控制标志: %d\r\n", control_flag);
|
||||||
|
} else {
|
||||||
|
printf("未识别的命令格式: %s\r\n", response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(1);
|
||||||
|
}
|
||||||
|
// 关闭socket
|
||||||
|
lwip_close(sockfd);
|
||||||
|
}
|
||||||
146
wifi_connecter.c
Normal file
146
wifi_connecter.c
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
#include "cmsis_os2.h" // CMSIS-RTOS API V2
|
||||||
|
#include "wifi_device.h" // Wi-Fi设备接口:station模式
|
||||||
|
|
||||||
|
#include "lwip/netifapi.h" // lwIP TCP/IP协议栈:网络接口API
|
||||||
|
#include "lwip/api_shell.h" // lwIP TCP/IP协议栈:SHELL命令API
|
||||||
|
|
||||||
|
static void PrintLinkedInfo(WifiLinkedInfo* info)
|
||||||
|
{
|
||||||
|
if (!info) return;
|
||||||
|
|
||||||
|
static char macAddress[32] = {0};
|
||||||
|
unsigned char* mac = info->bssid;
|
||||||
|
snprintf(macAddress, sizeof(macAddress), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
printf("bssid: %s, rssi: %d, connState: %d, reason: %d, ssid: %s\r\n",
|
||||||
|
macAddress, info->rssi, info->connState, info->disconnectedReason, info->ssid);
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile int g_connected = 0;
|
||||||
|
|
||||||
|
static void OnWifiConnectionChanged(int state, WifiLinkedInfo* info)
|
||||||
|
{
|
||||||
|
if (!info) return;
|
||||||
|
|
||||||
|
printf("%s %d, state = %d, info = \r\n", __FUNCTION__, __LINE__, state);
|
||||||
|
PrintLinkedInfo(info);
|
||||||
|
|
||||||
|
if (state == WIFI_STATE_AVALIABLE) {
|
||||||
|
g_connected = 1;
|
||||||
|
} else {
|
||||||
|
g_connected = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnWifiScanStateChanged(int state, int size)
|
||||||
|
{
|
||||||
|
printf("%s %d, state = %X, size = %d\r\n", __FUNCTION__, __LINE__, state, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static WifiEvent g_defaultWifiEventListener = {
|
||||||
|
.OnWifiConnectionChanged = OnWifiConnectionChanged,
|
||||||
|
.OnWifiScanStateChanged = OnWifiScanStateChanged
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct netif* g_iface = NULL;
|
||||||
|
|
||||||
|
err_t netifapi_set_hostname(struct netif *netif, char *hostname, u8_t namelen);
|
||||||
|
|
||||||
|
int ConnectToHotspot(WifiDeviceConfig* apConfig)
|
||||||
|
{
|
||||||
|
WifiErrorCode errCode;
|
||||||
|
int netId = -1;
|
||||||
|
|
||||||
|
errCode = RegisterWifiEvent(&g_defaultWifiEventListener);
|
||||||
|
printf("RegisterWifiEvent: %d\r\n", errCode);
|
||||||
|
|
||||||
|
errCode = EnableWifi();
|
||||||
|
printf("EnableWifi: %d\r\n", errCode);
|
||||||
|
|
||||||
|
errCode = AddDeviceConfig(apConfig, &netId);
|
||||||
|
printf("AddDeviceConfig: %d\r\n", errCode);
|
||||||
|
|
||||||
|
g_connected = 0;
|
||||||
|
errCode = ConnectTo(netId);
|
||||||
|
printf("ConnectTo(%d): %d\r\n", netId, errCode);
|
||||||
|
|
||||||
|
while (!g_connected) { // wait until connect to AP
|
||||||
|
// printf("connecting\n");
|
||||||
|
osDelay(10);
|
||||||
|
}
|
||||||
|
printf("g_connected: %d\r\n", g_connected);
|
||||||
|
|
||||||
|
g_iface = netifapi_netif_find("wlan0");
|
||||||
|
if (g_iface) {
|
||||||
|
err_t ret = 0;
|
||||||
|
char* hostname = "hispark";
|
||||||
|
ret = netifapi_set_hostname(g_iface, hostname, strlen(hostname));
|
||||||
|
printf("netifapi_set_hostname: %d\r\n", ret);
|
||||||
|
|
||||||
|
ret = netifapi_dhcp_start(g_iface);
|
||||||
|
printf("netifapi_dhcp_start: %d\r\n", ret);
|
||||||
|
|
||||||
|
osDelay(100); // wait DHCP server give me IP
|
||||||
|
#if 0
|
||||||
|
ret = netifapi_netif_common(g_iface, dhcp_clients_info_show, NULL);
|
||||||
|
printf("netifapi_netif_common: %d\r\n", ret);
|
||||||
|
#else
|
||||||
|
// 下面这种方式也可以打印 IP、网关、子网掩码信息
|
||||||
|
ip4_addr_t ip = {0};
|
||||||
|
ip4_addr_t netmask = {0};
|
||||||
|
ip4_addr_t gw = {0};
|
||||||
|
ret = netifapi_netif_get_addr(g_iface, &ip, &netmask, &gw);
|
||||||
|
if (ret == ERR_OK) {
|
||||||
|
printf("ip = %s\r\n", ip4addr_ntoa(&ip));
|
||||||
|
printf("netmask = %s\r\n", ip4addr_ntoa(&netmask));
|
||||||
|
printf("gw = %s\r\n", ip4addr_ntoa(&gw));
|
||||||
|
}
|
||||||
|
printf("netifapi_netif_get_addr: %d\r\n", ret);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return netId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DisconnectWithHotspot(int netId)
|
||||||
|
{
|
||||||
|
if (g_iface) {
|
||||||
|
err_t ret = netifapi_dhcp_stop(g_iface);
|
||||||
|
printf("netifapi_dhcp_stop: %d\r\n", ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
WifiErrorCode errCode = Disconnect(); // disconnect with your AP
|
||||||
|
printf("Disconnect: %d\r\n", errCode);
|
||||||
|
|
||||||
|
errCode = UnRegisterWifiEvent(&g_defaultWifiEventListener);
|
||||||
|
printf("UnRegisterWifiEvent: %d\r\n", errCode);
|
||||||
|
|
||||||
|
RemoveDevice(netId); // remove AP config
|
||||||
|
printf("RemoveDevice: %d\r\n", errCode);
|
||||||
|
|
||||||
|
errCode = DisableWifi();
|
||||||
|
printf("DisableWifi: %d\r\n", errCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取本机IP地址
|
||||||
|
int GetLocalIpAddress(char* ip_buffer, int buffer_size)
|
||||||
|
{
|
||||||
|
if (!ip_buffer || buffer_size < 16 || !g_iface) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ip4_addr_t ip = {0};
|
||||||
|
ip4_addr_t netmask = {0};
|
||||||
|
ip4_addr_t gw = {0};
|
||||||
|
|
||||||
|
err_t ret = netifapi_netif_get_addr(g_iface, &ip, &netmask, &gw);
|
||||||
|
if (ret == ERR_OK) {
|
||||||
|
const char* ip_str = ip4addr_ntoa(&ip);
|
||||||
|
if (ip_str && strlen(ip_str) < buffer_size) {
|
||||||
|
strcpy(ip_buffer, ip_str);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
13
wifi_connecter.h
Normal file
13
wifi_connecter.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef WIFI_CONNECTER_H
|
||||||
|
#define WIFI_CONNECTER_H
|
||||||
|
|
||||||
|
#include "wifi_device.h" // Wi-Fi设备接口:station模式
|
||||||
|
|
||||||
|
int ConnectToHotspot(WifiDeviceConfig* apConfig);
|
||||||
|
|
||||||
|
void DisconnectWithHotspot(int netId);
|
||||||
|
|
||||||
|
// 获取本机IP地址
|
||||||
|
int GetLocalIpAddress(char* ip_buffer, int buffer_size);
|
||||||
|
|
||||||
|
#endif // WIFI_CONNECTER_H
|
||||||
8
yolopart/.idea/.gitignore
generated
vendored
8
yolopart/.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
# 默认忽略的文件
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# 基于编辑器的 HTTP 客户端请求
|
|
||||||
/httpRequests/
|
|
||||||
# Datasource local storage ignored files
|
|
||||||
/dataSources/
|
|
||||||
/dataSources.local.xml
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
|
||||||
<settings>
|
|
||||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
|
||||||
<version value="1.0" />
|
|
||||||
</settings>
|
|
||||||
</component>
|
|
||||||
7
yolopart/.idea/misc.xml
generated
7
yolopart/.idea/misc.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="Black">
|
|
||||||
<option name="sdkName" value="pytorh" />
|
|
||||||
</component>
|
|
||||||
<component name="ProjectRootManager" version="2" project-jdk-name="pytorh" project-jdk-type="Python SDK" />
|
|
||||||
</project>
|
|
||||||
8
yolopart/.idea/modules.xml
generated
8
yolopart/.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ProjectModuleManager">
|
|
||||||
<modules>
|
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/yolopart.iml" filepath="$PROJECT_DIR$/.idea/yolopart.iml" />
|
|
||||||
</modules>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
yolopart/.idea/vcs.xml
generated
6
yolopart/.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
12
yolopart/.idea/yolopart.iml
generated
12
yolopart/.idea/yolopart.iml
generated
@@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<module type="PYTHON_MODULE" version="4">
|
|
||||||
<component name="NewModuleRootManager">
|
|
||||||
<content url="file://$MODULE_DIR$" />
|
|
||||||
<orderEntry type="jdk" jdkName="pytorh" jdkType="Python SDK" />
|
|
||||||
<orderEntry type="sourceFolder" forTests="false" />
|
|
||||||
</component>
|
|
||||||
<component name="PyDocumentationSettings">
|
|
||||||
<option name="format" value="PLAIN" />
|
|
||||||
<option name="myDocStringFormat" value="Plain" />
|
|
||||||
</component>
|
|
||||||
</module>
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
# 车牌检测系统
|
|
||||||
|
|
||||||
基于YOLO11s模型的实时车牌检测应用,支持摄像头和视频文件输入,具备GPU加速和车牌识别接口。
|
|
||||||
|
|
||||||
## 功能特性
|
|
||||||
|
|
||||||
- ✅ **实时车牌检测**: 基于YOLO11s ONNX模型
|
|
||||||
- ✅ **GPU加速**: 支持CUDA GPU推理加速
|
|
||||||
- ✅ **多视频源**: 支持摄像头和视频文件切换
|
|
||||||
- ✅ **实时显示**: 显示检测框、置信度和实时FPS
|
|
||||||
- ✅ **图像切割**: 自动切割检测到的车牌区域
|
|
||||||
- ✅ **识别接口**: 预留车牌号识别接口,可接入OCR模型
|
|
||||||
- ✅ **友好界面**: 基于PyQt5的现代化用户界面
|
|
||||||
|
|
||||||
## 系统要求
|
|
||||||
|
|
||||||
- Python 3.7+
|
|
||||||
- Windows/Linux/macOS
|
|
||||||
- 摄像头(可选)
|
|
||||||
- NVIDIA GPU(可选,用于加速)
|
|
||||||
|
|
||||||
## 安装依赖
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 安装基础依赖
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
# 如果需要CPU版本的onnxruntime
|
|
||||||
pip uninstall onnxruntime-gpu
|
|
||||||
pip install onnxruntime
|
|
||||||
|
|
||||||
# 可选:安装车牌识别依赖
|
|
||||||
# PaddleOCR
|
|
||||||
pip install paddlepaddle paddleocr
|
|
||||||
|
|
||||||
# 或者 Tesseract
|
|
||||||
pip install pytesseract
|
|
||||||
```
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
### 1. 准备模型文件
|
|
||||||
|
|
||||||
确保项目根目录下有以下文件:
|
|
||||||
- `last.onnx`: YOLO11s车牌检测模型
|
|
||||||
- `video.mp4`: 测试视频文件(可选)
|
|
||||||
|
|
||||||
### 2. 运行应用
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. 界面操作
|
|
||||||
|
|
||||||
- **开始检测**: 点击"开始检测"按钮启动实时检测
|
|
||||||
- **切换视频源**: 勾选/取消"使用摄像头"切换视频源
|
|
||||||
- **启用检测**: 勾选/取消"启用检测"开关检测功能
|
|
||||||
- **查看结果**: 右侧面板显示检测信息和车牌识别结果
|
|
||||||
|
|
||||||
## 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
yolopart/
|
|
||||||
├── main.py # 主程序入口
|
|
||||||
├── requirements.txt # 依赖包列表
|
|
||||||
├── README.md # 项目说明
|
|
||||||
├── last.onnx # YOLO11s模型文件
|
|
||||||
├── video.mp4 # 测试视频文件
|
|
||||||
├── ui/ # 用户界面模块
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── main_window.py # 主窗口
|
|
||||||
│ └── video_widget.py # 视频显示组件
|
|
||||||
├── models/ # 模型模块
|
|
||||||
│ ├── __init__.py
|
|
||||||
│ ├── yolo_detector.py # YOLO检测器
|
|
||||||
│ └── plate_recognizer.py # 车牌识别接口
|
|
||||||
└── utils/ # 工具模块
|
|
||||||
├── __init__.py
|
|
||||||
└── video_capture.py # 视频捕获管理
|
|
||||||
```
|
|
||||||
|
|
||||||
## 核心功能说明
|
|
||||||
|
|
||||||
### YOLO检测器 (`models/yolo_detector.py`)
|
|
||||||
|
|
||||||
- 支持ONNX格式的YOLO11s模型
|
|
||||||
- 自动GPU/CPU推理选择
|
|
||||||
- 640x640输入尺寸
|
|
||||||
- NMS后处理
|
|
||||||
- 检测框绘制和车牌切割
|
|
||||||
|
|
||||||
### 视频捕获 (`utils/video_capture.py`)
|
|
||||||
|
|
||||||
- 摄像头自动检测和配置
|
|
||||||
- 视频文件循环播放
|
|
||||||
- 实时FPS计算和显示
|
|
||||||
- 线程安全的帧获取
|
|
||||||
|
|
||||||
### 车牌识别接口 (`models/plate_recognizer.py`)
|
|
||||||
|
|
||||||
提供了多种识别器实现:
|
|
||||||
- `MockPlateRecognizer`: 模拟识别器(用于测试)
|
|
||||||
- `PaddleOCRRecognizer`: PaddleOCR识别器
|
|
||||||
- `TesseractRecognizer`: Tesseract识别器
|
|
||||||
|
|
||||||
可通过`PlateRecognizerManager`轻松切换不同的识别引擎。
|
|
||||||
|
|
||||||
## 配置说明
|
|
||||||
|
|
||||||
### 检测参数调整
|
|
||||||
|
|
||||||
在`models/yolo_detector.py`中可以调整:
|
|
||||||
- `conf_threshold`: 置信度阈值(默认0.5)
|
|
||||||
- `nms_threshold`: NMS阈值(默认0.4)
|
|
||||||
- `input_size`: 输入尺寸(默认640x640)
|
|
||||||
|
|
||||||
### 视频参数调整
|
|
||||||
|
|
||||||
在`utils/video_capture.py`中可以调整:
|
|
||||||
- 摄像头分辨率和帧率
|
|
||||||
- FPS计算窗口大小
|
|
||||||
- 视频文件路径
|
|
||||||
|
|
||||||
## 扩展开发
|
|
||||||
|
|
||||||
### 添加新的车牌识别器
|
|
||||||
|
|
||||||
1. 继承`PlateRecognizerInterface`基类
|
|
||||||
2. 实现`recognize`和`batch_recognize`方法
|
|
||||||
3. 在`PlateRecognizerManager`中注册新识别器
|
|
||||||
|
|
||||||
### 添加新功能
|
|
||||||
|
|
||||||
- 检测结果保存
|
|
||||||
- 车牌数据库管理
|
|
||||||
- 网络接口API
|
|
||||||
- 多摄像头支持
|
|
||||||
|
|
||||||
## 故障排除
|
|
||||||
|
|
||||||
### 常见问题
|
|
||||||
|
|
||||||
1. **模型加载失败**
|
|
||||||
- 检查`last.onnx`文件是否存在
|
|
||||||
- 确认onnxruntime版本兼容性
|
|
||||||
|
|
||||||
2. **摄像头无法打开**
|
|
||||||
- 检查摄像头是否被其他程序占用
|
|
||||||
- 尝试不同的摄像头索引
|
|
||||||
|
|
||||||
3. **GPU加速不生效**
|
|
||||||
- 确认安装了`onnxruntime-gpu`
|
|
||||||
- 检查CUDA环境配置
|
|
||||||
|
|
||||||
4. **车牌识别失败**
|
|
||||||
- 检查OCR依赖是否正确安装
|
|
||||||
- 尝试切换不同的识别器
|
|
||||||
|
|
||||||
### 性能优化
|
|
||||||
|
|
||||||
- 使用GPU加速推理
|
|
||||||
- 调整检测阈值减少误检
|
|
||||||
- 优化图像预处理流程
|
|
||||||
- 使用多线程处理
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
本项目仅供学习和研究使用。
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
### v1.0.0
|
|
||||||
- 初始版本发布
|
|
||||||
- 支持YOLO11s车牌检测
|
|
||||||
- 实现基础UI界面
|
|
||||||
- 预留车牌识别接口
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
车牌检测系统主程序
|
|
||||||
基于YOLO11s模型的实时车牌检测应用
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from PyQt5.QtWidgets import QApplication
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
from ui.main_window import MainWindow
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""主函数"""
|
|
||||||
# 创建QApplication实例
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
app.setAttribute(Qt.AA_EnableHighDpiScaling, True)
|
|
||||||
app.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
|
|
||||||
|
|
||||||
# 设置应用信息
|
|
||||||
app.setApplicationName("车牌检测系统")
|
|
||||||
app.setApplicationVersion("1.0.0")
|
|
||||||
app.setOrganizationName("License Plate Detection")
|
|
||||||
|
|
||||||
# 创建主窗口
|
|
||||||
main_window = MainWindow()
|
|
||||||
main_window.show()
|
|
||||||
|
|
||||||
# 运行应用
|
|
||||||
sys.exit(app.exec_())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# 模型模块初始化文件
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,490 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
车牌识别接口模块
|
|
||||||
预留接口,可接入各种OCR模型进行车牌号识别
|
|
||||||
"""
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
from typing import List, Optional, Dict, Any
|
|
||||||
from abc import ABC, abstractmethod
|
|
||||||
|
|
||||||
class PlateRecognizerInterface(ABC):
|
|
||||||
"""车牌识别接口基类"""
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def recognize(self, plate_image: np.ndarray) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
识别车牌号
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_image: 车牌图像 (BGR格式)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果字典,包含:
|
|
||||||
{
|
|
||||||
'text': str, # 识别的车牌号
|
|
||||||
'confidence': float, # 置信度 (0-1)
|
|
||||||
'success': bool # 是否识别成功
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
def batch_recognize(self, plate_images: List[np.ndarray]) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
批量识别车牌号
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_images: 车牌图像列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果列表
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
class MockPlateRecognizer(PlateRecognizerInterface):
|
|
||||||
"""模拟车牌识别器(用于测试)"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.mock_plates = [
|
|
||||||
"京A12345", "沪B67890", "粤C11111", "川D22222",
|
|
||||||
"鲁E33333", "苏F44444", "浙G55555", "闽H66666"
|
|
||||||
]
|
|
||||||
self.call_count = 0
|
|
||||||
|
|
||||||
def recognize(self, plate_image: np.ndarray) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
模拟识别单个车牌
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_image: 车牌图像
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
模拟识别结果
|
|
||||||
"""
|
|
||||||
# 模拟处理时间
|
|
||||||
import time
|
|
||||||
time.sleep(0.01) # 10ms模拟处理时间
|
|
||||||
|
|
||||||
# 简单的图像质量检查
|
|
||||||
if plate_image is None or plate_image.size == 0:
|
|
||||||
return {
|
|
||||||
'text': '',
|
|
||||||
'confidence': 0.0,
|
|
||||||
'success': False
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查图像尺寸
|
|
||||||
height, width = plate_image.shape[:2]
|
|
||||||
if width < 50 or height < 20:
|
|
||||||
return {
|
|
||||||
'text': '',
|
|
||||||
'confidence': 0.3,
|
|
||||||
'success': False
|
|
||||||
}
|
|
||||||
|
|
||||||
# 模拟识别结果
|
|
||||||
plate_text = self.mock_plates[self.call_count % len(self.mock_plates)]
|
|
||||||
confidence = 0.85 + (self.call_count % 10) * 0.01 # 0.85-0.94
|
|
||||||
|
|
||||||
self.call_count += 1
|
|
||||||
|
|
||||||
return {
|
|
||||||
'text': plate_text,
|
|
||||||
'confidence': confidence,
|
|
||||||
'success': True
|
|
||||||
}
|
|
||||||
|
|
||||||
def batch_recognize(self, plate_images: List[np.ndarray]) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
批量识别车牌
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_images: 车牌图像列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果列表
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for plate_image in plate_images:
|
|
||||||
result = self.recognize(plate_image)
|
|
||||||
results.append(result)
|
|
||||||
return results
|
|
||||||
|
|
||||||
class PaddleOCRRecognizer(PlateRecognizerInterface):
|
|
||||||
"""PaddleOCR车牌识别器(示例实现)"""
|
|
||||||
|
|
||||||
def __init__(self, use_gpu: bool = True):
|
|
||||||
"""
|
|
||||||
初始化PaddleOCR识别器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
use_gpu: 是否使用GPU
|
|
||||||
"""
|
|
||||||
self.use_gpu = use_gpu
|
|
||||||
self.ocr = None
|
|
||||||
self._init_ocr()
|
|
||||||
|
|
||||||
def _init_ocr(self):
|
|
||||||
"""初始化OCR模型"""
|
|
||||||
try:
|
|
||||||
# 这里可以接入PaddleOCR
|
|
||||||
# from paddleocr import PaddleOCR
|
|
||||||
# self.ocr = PaddleOCR(use_angle_cls=True, lang='ch', use_gpu=self.use_gpu)
|
|
||||||
print("PaddleOCR初始化完成(示例代码,需要安装PaddleOCR)")
|
|
||||||
except ImportError:
|
|
||||||
print("PaddleOCR未安装,使用模拟识别器")
|
|
||||||
self.ocr = None
|
|
||||||
|
|
||||||
def recognize(self, plate_image: np.ndarray) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
使用PaddleOCR识别车牌
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_image: 车牌图像
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果
|
|
||||||
"""
|
|
||||||
if self.ocr is None:
|
|
||||||
# 回退到模拟识别
|
|
||||||
mock_recognizer = MockPlateRecognizer()
|
|
||||||
return mock_recognizer.recognize(plate_image)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# 使用PaddleOCR进行识别
|
|
||||||
results = self.ocr.ocr(plate_image, cls=True)
|
|
||||||
|
|
||||||
if results and len(results) > 0 and results[0]:
|
|
||||||
# 提取文本和置信度
|
|
||||||
text_results = []
|
|
||||||
for line in results[0]:
|
|
||||||
text = line[1][0]
|
|
||||||
confidence = line[1][1]
|
|
||||||
text_results.append((text, confidence))
|
|
||||||
|
|
||||||
# 选择置信度最高的结果
|
|
||||||
if text_results:
|
|
||||||
best_result = max(text_results, key=lambda x: x[1])
|
|
||||||
return {
|
|
||||||
'text': best_result[0],
|
|
||||||
'confidence': best_result[1],
|
|
||||||
'success': True
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"PaddleOCR识别失败: {e}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'text': '',
|
|
||||||
'confidence': 0.0,
|
|
||||||
'success': False
|
|
||||||
}
|
|
||||||
|
|
||||||
def batch_recognize(self, plate_images: List[np.ndarray]) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
批量识别
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_images: 车牌图像列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果列表
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for plate_image in plate_images:
|
|
||||||
result = self.recognize(plate_image)
|
|
||||||
results.append(result)
|
|
||||||
return results
|
|
||||||
|
|
||||||
class TesseractRecognizer(PlateRecognizerInterface):
|
|
||||||
"""Tesseract车牌识别器(示例实现)"""
|
|
||||||
|
|
||||||
def __init__(self, lang: str = 'chi_sim+eng'):
|
|
||||||
"""
|
|
||||||
初始化Tesseract识别器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
lang: 识别语言
|
|
||||||
"""
|
|
||||||
self.lang = lang
|
|
||||||
self.tesseract_available = self._check_tesseract()
|
|
||||||
|
|
||||||
def _check_tesseract(self) -> bool:
|
|
||||||
"""检查Tesseract是否可用"""
|
|
||||||
try:
|
|
||||||
import pytesseract
|
|
||||||
return True
|
|
||||||
except ImportError:
|
|
||||||
print("pytesseract未安装,使用模拟识别器")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def recognize(self, plate_image: np.ndarray) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
使用Tesseract识别车牌
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_image: 车牌图像
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果
|
|
||||||
"""
|
|
||||||
if not self.tesseract_available:
|
|
||||||
# 回退到模拟识别
|
|
||||||
mock_recognizer = MockPlateRecognizer()
|
|
||||||
return mock_recognizer.recognize(plate_image)
|
|
||||||
|
|
||||||
try:
|
|
||||||
import pytesseract
|
|
||||||
|
|
||||||
# 图像预处理
|
|
||||||
processed_image = self._preprocess_image(plate_image)
|
|
||||||
|
|
||||||
# 使用Tesseract识别
|
|
||||||
text = pytesseract.image_to_string(
|
|
||||||
processed_image,
|
|
||||||
lang=self.lang,
|
|
||||||
config='--psm 8 --oem 3 -c tessedit_char_whitelist=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ京沪粤川鲁苏浙闽'
|
|
||||||
)
|
|
||||||
|
|
||||||
# 清理识别结果
|
|
||||||
text = text.strip().replace(' ', '').replace('\n', '')
|
|
||||||
|
|
||||||
if text and len(text) >= 5: # 车牌号至少5位
|
|
||||||
return {
|
|
||||||
'text': text,
|
|
||||||
'confidence': 0.8, # Tesseract不直接提供置信度
|
|
||||||
'success': True
|
|
||||||
}
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Tesseract识别失败: {e}")
|
|
||||||
|
|
||||||
return {
|
|
||||||
'text': '',
|
|
||||||
'confidence': 0.0,
|
|
||||||
'success': False
|
|
||||||
}
|
|
||||||
|
|
||||||
def _preprocess_image(self, image: np.ndarray) -> np.ndarray:
|
|
||||||
"""图像预处理"""
|
|
||||||
# 转换为灰度图
|
|
||||||
if len(image.shape) == 3:
|
|
||||||
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
|
||||||
else:
|
|
||||||
gray = image
|
|
||||||
|
|
||||||
# 调整尺寸
|
|
||||||
height, width = gray.shape
|
|
||||||
if width < 200:
|
|
||||||
scale = 200 / width
|
|
||||||
new_width = int(width * scale)
|
|
||||||
new_height = int(height * scale)
|
|
||||||
gray = cv2.resize(gray, (new_width, new_height))
|
|
||||||
|
|
||||||
# 二值化
|
|
||||||
_, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
|
||||||
|
|
||||||
return binary
|
|
||||||
|
|
||||||
def batch_recognize(self, plate_images: List[np.ndarray]) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
批量识别
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_images: 车牌图像列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果列表
|
|
||||||
"""
|
|
||||||
results = []
|
|
||||||
for plate_image in plate_images:
|
|
||||||
result = self.recognize(plate_image)
|
|
||||||
results.append(result)
|
|
||||||
return results
|
|
||||||
|
|
||||||
class PlateRecognizerManager:
|
|
||||||
"""车牌识别管理器"""
|
|
||||||
|
|
||||||
def __init__(self, recognizer_type: str = 'mock'):
|
|
||||||
"""
|
|
||||||
初始化识别管理器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
recognizer_type: 识别器类型 ('mock', 'paddleocr', 'tesseract')
|
|
||||||
"""
|
|
||||||
self.recognizer_type = recognizer_type
|
|
||||||
self.recognizer = self._create_recognizer(recognizer_type)
|
|
||||||
|
|
||||||
def _create_recognizer(self, recognizer_type: str) -> PlateRecognizerInterface:
|
|
||||||
"""创建识别器"""
|
|
||||||
if recognizer_type == 'mock':
|
|
||||||
return MockPlateRecognizer()
|
|
||||||
elif recognizer_type == 'paddleocr':
|
|
||||||
return PaddleOCRRecognizer()
|
|
||||||
elif recognizer_type == 'tesseract':
|
|
||||||
return TesseractRecognizer()
|
|
||||||
else:
|
|
||||||
print(f"未知的识别器类型: {recognizer_type},使用模拟识别器")
|
|
||||||
return MockPlateRecognizer()
|
|
||||||
|
|
||||||
def recognize_plates(self, plate_images: List[np.ndarray]) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
识别车牌列表
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_images: 车牌图像列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别结果列表
|
|
||||||
"""
|
|
||||||
if not plate_images:
|
|
||||||
return []
|
|
||||||
|
|
||||||
return self.recognizer.batch_recognize(plate_images)
|
|
||||||
|
|
||||||
def switch_recognizer(self, recognizer_type: str):
|
|
||||||
"""
|
|
||||||
切换识别器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
recognizer_type: 新的识别器类型
|
|
||||||
"""
|
|
||||||
self.recognizer_type = recognizer_type
|
|
||||||
self.recognizer = self._create_recognizer(recognizer_type)
|
|
||||||
print(f"已切换到识别器: {recognizer_type}")
|
|
||||||
|
|
||||||
def get_recognizer_info(self) -> Dict[str, Any]:
|
|
||||||
"""
|
|
||||||
获取识别器信息
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
识别器信息
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
'type': self.recognizer_type,
|
|
||||||
'class': self.recognizer.__class__.__name__
|
|
||||||
}
|
|
||||||
|
|
||||||
def preprocess_blue_plate(self, plate_image: np.ndarray, original_image: np.ndarray, bbox: List[int]) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
蓝色车牌预处理:倾斜矫正
|
|
||||||
|
|
||||||
Args:
|
|
||||||
plate_image: 切割后的车牌图像
|
|
||||||
original_image: 原始图像
|
|
||||||
bbox: 边界框坐标 [x1, y1, x2, y2]
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
矫正后的车牌图像
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 从原图中提取车牌区域
|
|
||||||
x1, y1, x2, y2 = bbox
|
|
||||||
roi = original_image[y1:y2, x1:x2]
|
|
||||||
|
|
||||||
# 获取蓝色车牌的二值图像
|
|
||||||
bin_img = self._get_blue_img_bin(roi)
|
|
||||||
|
|
||||||
# 倾斜矫正
|
|
||||||
corrected_img = self._deskew_plate(bin_img, roi)
|
|
||||||
|
|
||||||
return corrected_img
|
|
||||||
except Exception as e:
|
|
||||||
print(f"蓝色车牌预处理失败: {e}")
|
|
||||||
return plate_image
|
|
||||||
|
|
||||||
def _get_blue_img_bin(self, img: np.ndarray) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
获取蓝色车牌的二值图像
|
|
||||||
"""
|
|
||||||
# 掩膜:BGR通道,若像素B分量在 100~255 且 G分量在 0~190 且 R分量在 0~140 置255(白色),否则置0(黑色)
|
|
||||||
mask_bgr = cv2.inRange(img, (100, 0, 0), (255, 190, 140))
|
|
||||||
|
|
||||||
# 转换成 HSV 颜色空间
|
|
||||||
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
|
|
||||||
h, s, v = cv2.split(img_hsv) # 分离通道 色调(H),饱和度(S),明度(V)
|
|
||||||
mask_s = cv2.inRange(s, 80, 255) # 取饱和度通道进行掩膜得到二值图像
|
|
||||||
|
|
||||||
# 与操作,两个二值图像都为白色才保留,否则置黑
|
|
||||||
rgbs = mask_bgr & mask_s
|
|
||||||
|
|
||||||
# 核的横向分量大,使车牌数字尽量连在一起
|
|
||||||
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 3))
|
|
||||||
img_rgbs_dilate = cv2.dilate(rgbs, kernel, 3) # 膨胀,减小车牌空洞
|
|
||||||
|
|
||||||
return img_rgbs_dilate
|
|
||||||
|
|
||||||
def _order_points(self, pts: np.ndarray) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
将四点按 左上、右上、右下、左下 排序
|
|
||||||
"""
|
|
||||||
rect = np.zeros((4, 2), dtype="float32")
|
|
||||||
s = pts.sum(axis=1)
|
|
||||||
rect[0] = pts[np.argmin(s)] # 左上
|
|
||||||
rect[2] = pts[np.argmax(s)] # 右下
|
|
||||||
|
|
||||||
diff = np.diff(pts, axis=1)
|
|
||||||
rect[1] = pts[np.argmin(diff)] # 右上
|
|
||||||
rect[3] = pts[np.argmax(diff)] # 左下
|
|
||||||
|
|
||||||
return rect
|
|
||||||
|
|
||||||
def _deskew_plate(self, bin_img: np.ndarray, original_roi: np.ndarray) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
车牌倾斜矫正
|
|
||||||
|
|
||||||
Args:
|
|
||||||
bin_img: 二值图像
|
|
||||||
original_roi: 原始ROI区域
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
矫正后的原始图像(未被掩模,但经过旋转和切割)
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 找最大轮廓
|
|
||||||
cnts, _ = cv2.findContours(bin_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
||||||
if not cnts:
|
|
||||||
return original_roi
|
|
||||||
|
|
||||||
c = max(cnts, key=cv2.contourArea)
|
|
||||||
|
|
||||||
# 最小外接矩形
|
|
||||||
rect = cv2.minAreaRect(c)
|
|
||||||
box = cv2.boxPoints(rect)
|
|
||||||
box = np.array(box, dtype="float32")
|
|
||||||
|
|
||||||
# 排序四个点
|
|
||||||
pts_src = self._order_points(box)
|
|
||||||
|
|
||||||
# 计算目标矩形宽高
|
|
||||||
(tl, tr, br, bl) = pts_src
|
|
||||||
widthA = np.linalg.norm(br - bl)
|
|
||||||
widthB = np.linalg.norm(tr - tl)
|
|
||||||
maxWidth = int(max(widthA, widthB))
|
|
||||||
|
|
||||||
heightA = np.linalg.norm(tr - br)
|
|
||||||
heightB = np.linalg.norm(tl - bl)
|
|
||||||
maxHeight = int(max(heightA, heightB))
|
|
||||||
|
|
||||||
# 确保尺寸合理
|
|
||||||
if maxWidth < 10 or maxHeight < 10:
|
|
||||||
return original_roi
|
|
||||||
|
|
||||||
# 目标点集合
|
|
||||||
pts_dst = np.array([
|
|
||||||
[0, 0],
|
|
||||||
[maxWidth - 1, 0],
|
|
||||||
[maxWidth - 1, maxHeight - 1],
|
|
||||||
[0, maxHeight - 1]], dtype="float32")
|
|
||||||
|
|
||||||
# 透视变换
|
|
||||||
M = cv2.getPerspectiveTransform(pts_src, pts_dst)
|
|
||||||
warped = cv2.warpPerspective(original_roi, M, (maxWidth, maxHeight))
|
|
||||||
|
|
||||||
return warped
|
|
||||||
except Exception as e:
|
|
||||||
print(f"车牌矫正失败: {e}")
|
|
||||||
return original_roi
|
|
||||||
@@ -1,368 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
YOLO车牌检测器
|
|
||||||
基于ONNX Runtime的YOLO11s模型推理
|
|
||||||
"""
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
import onnxruntime as ort
|
|
||||||
import time
|
|
||||||
from typing import List, Tuple, Optional
|
|
||||||
|
|
||||||
class YOLODetector:
|
|
||||||
"""YOLO车牌检测器"""
|
|
||||||
|
|
||||||
def __init__(self, model_path: str, conf_threshold: float = 0.25, nms_threshold: float = 0.4):
|
|
||||||
"""
|
|
||||||
初始化YOLO检测器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
model_path: ONNX模型文件路径
|
|
||||||
conf_threshold: 置信度阈值
|
|
||||||
nms_threshold: NMS阈值
|
|
||||||
"""
|
|
||||||
self.model_path = model_path
|
|
||||||
self.conf_threshold = conf_threshold
|
|
||||||
self.nms_threshold = nms_threshold
|
|
||||||
self.input_size = (640, 640) # YOLO11s输入尺寸
|
|
||||||
self.use_gpu = False
|
|
||||||
|
|
||||||
# 初始化ONNX Runtime会话
|
|
||||||
self._init_session()
|
|
||||||
|
|
||||||
# 获取模型输入输出信息
|
|
||||||
self.input_name = self.session.get_inputs()[0].name
|
|
||||||
self.output_names = [output.name for output in self.session.get_outputs()]
|
|
||||||
|
|
||||||
print(f"YOLO检测器初始化完成")
|
|
||||||
print(f"模型路径: {model_path}")
|
|
||||||
print(f"输入尺寸: {self.input_size}")
|
|
||||||
print(f"GPU加速: {self.use_gpu}")
|
|
||||||
|
|
||||||
def _init_session(self):
|
|
||||||
"""初始化ONNX Runtime会话"""
|
|
||||||
# 获取可用的providers
|
|
||||||
available_providers = ort.get_available_providers()
|
|
||||||
print(f"可用的执行提供者: {available_providers}")
|
|
||||||
|
|
||||||
# 优先使用GPU,如果可用的话
|
|
||||||
providers = []
|
|
||||||
if 'CUDAExecutionProvider' in available_providers:
|
|
||||||
providers.append('CUDAExecutionProvider')
|
|
||||||
self.use_gpu = True
|
|
||||||
print("检测到CUDA支持,将使用GPU加速")
|
|
||||||
elif 'TensorrtExecutionProvider' in available_providers:
|
|
||||||
providers.append('TensorrtExecutionProvider')
|
|
||||||
self.use_gpu = True
|
|
||||||
print("检测到TensorRT支持,将使用GPU加速")
|
|
||||||
else:
|
|
||||||
self.use_gpu = False
|
|
||||||
print("未检测到GPU支持,将使用CPU")
|
|
||||||
|
|
||||||
# 添加CPU作为备选
|
|
||||||
providers.append('CPUExecutionProvider')
|
|
||||||
|
|
||||||
print(f"使用的执行提供者: {providers}")
|
|
||||||
|
|
||||||
# 创建会话
|
|
||||||
session_options = ort.SessionOptions()
|
|
||||||
session_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
|
||||||
|
|
||||||
try:
|
|
||||||
self.session = ort.InferenceSession(
|
|
||||||
self.model_path,
|
|
||||||
sess_options=session_options,
|
|
||||||
providers=providers
|
|
||||||
)
|
|
||||||
|
|
||||||
# 检查实际使用的provider
|
|
||||||
actual_providers = self.session.get_providers()
|
|
||||||
print(f"实际使用的执行提供者: {actual_providers}")
|
|
||||||
|
|
||||||
if 'CUDAExecutionProvider' in actual_providers or 'TensorrtExecutionProvider' in actual_providers:
|
|
||||||
self.use_gpu = True
|
|
||||||
print("✅ GPU加速已启用")
|
|
||||||
else:
|
|
||||||
self.use_gpu = False
|
|
||||||
print("⚠️ 使用CPU执行")
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"模型加载失败: {e}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
def preprocess(self, image: np.ndarray) -> Tuple[np.ndarray, float, float]:
|
|
||||||
"""
|
|
||||||
图像预处理
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image: 输入图像 (BGR格式)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
preprocessed_image: 预处理后的图像
|
|
||||||
scale_x: X轴缩放比例
|
|
||||||
scale_y: Y轴缩放比例
|
|
||||||
"""
|
|
||||||
original_height, original_width = image.shape[:2]
|
|
||||||
target_width, target_height = self.input_size
|
|
||||||
|
|
||||||
# 计算缩放比例
|
|
||||||
scale_x = target_width / original_width
|
|
||||||
scale_y = target_height / original_height
|
|
||||||
|
|
||||||
# 等比例缩放
|
|
||||||
scale = min(scale_x, scale_y)
|
|
||||||
new_width = int(original_width * scale)
|
|
||||||
new_height = int(original_height * scale)
|
|
||||||
|
|
||||||
# 缩放图像
|
|
||||||
resized_image = cv2.resize(image, (new_width, new_height))
|
|
||||||
|
|
||||||
# 创建目标尺寸的图像并居中放置
|
|
||||||
padded_image = np.full((target_height, target_width, 3), 114, dtype=np.uint8)
|
|
||||||
|
|
||||||
# 计算填充位置
|
|
||||||
start_x = (target_width - new_width) // 2
|
|
||||||
start_y = (target_height - new_height) // 2
|
|
||||||
|
|
||||||
padded_image[start_y:start_y + new_height, start_x:start_x + new_width] = resized_image
|
|
||||||
|
|
||||||
# 转换为RGB并归一化
|
|
||||||
rgb_image = cv2.cvtColor(padded_image, cv2.COLOR_BGR2RGB)
|
|
||||||
normalized_image = rgb_image.astype(np.float32) / 255.0
|
|
||||||
|
|
||||||
# 转换为NCHW格式
|
|
||||||
input_tensor = np.transpose(normalized_image, (2, 0, 1))
|
|
||||||
input_tensor = np.expand_dims(input_tensor, axis=0)
|
|
||||||
|
|
||||||
return input_tensor, scale, scale
|
|
||||||
|
|
||||||
def postprocess(self, outputs: List[np.ndarray], scale_x: float, scale_y: float,
|
|
||||||
original_shape: Tuple[int, int]) -> List[dict]:
|
|
||||||
"""
|
|
||||||
后处理检测结果
|
|
||||||
|
|
||||||
Args:
|
|
||||||
outputs: 模型输出
|
|
||||||
scale_x: X轴缩放比例
|
|
||||||
scale_y: Y轴缩放比例
|
|
||||||
original_shape: 原始图像尺寸 (height, width)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
检测结果列表
|
|
||||||
"""
|
|
||||||
detections = []
|
|
||||||
|
|
||||||
if len(outputs) == 0:
|
|
||||||
return detections
|
|
||||||
|
|
||||||
# 获取输出张量
|
|
||||||
output = outputs[0]
|
|
||||||
|
|
||||||
# YOLO11输出格式: [batch, 6, 8400] -> [batch, 8400, 6]
|
|
||||||
if len(output.shape) == 3:
|
|
||||||
output = output.transpose(0, 2, 1)
|
|
||||||
|
|
||||||
# 处理每个检测结果
|
|
||||||
for detection in output[0]: # 取第一个batch
|
|
||||||
# 前4个值是边界框坐标,后2个是类别概率
|
|
||||||
x_center, y_center, width, height = detection[:4]
|
|
||||||
class_scores = detection[4:] # 类别概率 [蓝牌概率, 绿牌概率]
|
|
||||||
|
|
||||||
# 获取最高概率的类别
|
|
||||||
class_id = np.argmax(class_scores)
|
|
||||||
confidence = class_scores[class_id] # 使用类别概率作为置信度
|
|
||||||
|
|
||||||
# 过滤低置信度检测
|
|
||||||
if confidence < self.conf_threshold:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 转换坐标到原始图像尺寸
|
|
||||||
original_height, original_width = original_shape
|
|
||||||
|
|
||||||
# 计算实际缩放比例和偏移
|
|
||||||
scale = min(self.input_size[0] / original_width, self.input_size[1] / original_height)
|
|
||||||
pad_x = (self.input_size[0] - original_width * scale) / 2
|
|
||||||
pad_y = (self.input_size[1] - original_height * scale) / 2
|
|
||||||
|
|
||||||
# 转换坐标
|
|
||||||
x_center = (x_center - pad_x) / scale
|
|
||||||
y_center = (y_center - pad_y) / scale
|
|
||||||
width = width / scale
|
|
||||||
height = height / scale
|
|
||||||
|
|
||||||
# 计算边界框
|
|
||||||
x1 = int(x_center - width / 2)
|
|
||||||
y1 = int(y_center - height / 2)
|
|
||||||
x2 = int(x_center + width / 2)
|
|
||||||
y2 = int(y_center + height / 2)
|
|
||||||
|
|
||||||
# 确保坐标在图像范围内
|
|
||||||
x1 = max(0, min(x1, original_width - 1))
|
|
||||||
y1 = max(0, min(y1, original_height - 1))
|
|
||||||
x2 = max(0, min(x2, original_width - 1))
|
|
||||||
y2 = max(0, min(y2, original_height - 1))
|
|
||||||
|
|
||||||
# 定义类别名称
|
|
||||||
class_names = ['blue_plate', 'green_plate'] # 0: 蓝牌, 1: 绿牌
|
|
||||||
class_name = class_names[class_id] if class_id < len(class_names) else 'unknown'
|
|
||||||
|
|
||||||
detections.append({
|
|
||||||
'bbox': [x1, y1, x2, y2],
|
|
||||||
'confidence': float(confidence),
|
|
||||||
'class_id': int(class_id),
|
|
||||||
'class_name': class_name
|
|
||||||
})
|
|
||||||
|
|
||||||
# 应用NMS
|
|
||||||
if detections:
|
|
||||||
detections = self._apply_nms(detections)
|
|
||||||
|
|
||||||
return detections
|
|
||||||
|
|
||||||
def _apply_nms(self, detections: List[dict]) -> List[dict]:
|
|
||||||
"""
|
|
||||||
应用非极大值抑制
|
|
||||||
|
|
||||||
Args:
|
|
||||||
detections: 检测结果列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
NMS后的检测结果
|
|
||||||
"""
|
|
||||||
if len(detections) == 0:
|
|
||||||
return detections
|
|
||||||
|
|
||||||
# 提取边界框和置信度
|
|
||||||
boxes = np.array([det['bbox'] for det in detections])
|
|
||||||
scores = np.array([det['confidence'] for det in detections])
|
|
||||||
|
|
||||||
# 应用NMS
|
|
||||||
indices = cv2.dnn.NMSBoxes(
|
|
||||||
boxes.tolist(),
|
|
||||||
scores.tolist(),
|
|
||||||
self.conf_threshold,
|
|
||||||
self.nms_threshold
|
|
||||||
)
|
|
||||||
|
|
||||||
# 返回保留的检测结果
|
|
||||||
if len(indices) > 0:
|
|
||||||
indices = indices.flatten()
|
|
||||||
return [detections[i] for i in indices]
|
|
||||||
else:
|
|
||||||
return []
|
|
||||||
|
|
||||||
def detect(self, image: np.ndarray) -> List[dict]:
|
|
||||||
"""
|
|
||||||
检测车牌
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image: 输入图像 (BGR格式)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
检测结果列表
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
# 预处理
|
|
||||||
input_tensor, scale_x, scale_y = self.preprocess(image)
|
|
||||||
|
|
||||||
# 推理
|
|
||||||
outputs = self.session.run(self.output_names, {self.input_name: input_tensor})
|
|
||||||
|
|
||||||
# 调试输出
|
|
||||||
print(f"模型输出数量: {len(outputs)}")
|
|
||||||
for i, output in enumerate(outputs):
|
|
||||||
print(f"输出 {i} 形状: {output.shape}")
|
|
||||||
print(f"输出 {i} 数据范围: [{output.min():.4f}, {output.max():.4f}]")
|
|
||||||
|
|
||||||
# 后处理
|
|
||||||
detections = self.postprocess(outputs, scale_x, scale_y, image.shape[:2])
|
|
||||||
print(f"检测到的目标数量: {len(detections)}")
|
|
||||||
for i, det in enumerate(detections):
|
|
||||||
print(f"检测 {i}: 类别={det['class_name']}, 置信度={det['confidence']:.3f}")
|
|
||||||
|
|
||||||
return detections
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"检测过程出错: {e}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def draw_detections(self, image: np.ndarray, detections: List[dict]) -> np.ndarray:
|
|
||||||
"""
|
|
||||||
在图像上绘制检测结果
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image: 输入图像
|
|
||||||
detections: 检测结果列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
绘制了检测框的图像
|
|
||||||
"""
|
|
||||||
result_image = image.copy()
|
|
||||||
|
|
||||||
for detection in detections:
|
|
||||||
bbox = detection['bbox']
|
|
||||||
confidence = detection['confidence']
|
|
||||||
class_id = detection['class_id']
|
|
||||||
class_name = detection['class_name']
|
|
||||||
|
|
||||||
x1, y1, x2, y2 = bbox
|
|
||||||
|
|
||||||
# 根据车牌类型选择颜色
|
|
||||||
if class_id == 0: # 蓝牌
|
|
||||||
color = (255, 0, 0) # 蓝色 (BGR格式)
|
|
||||||
plate_type = "Blue Plate"
|
|
||||||
elif class_id == 1: # 绿牌
|
|
||||||
color = (0, 255, 0) # 绿色 (BGR格式)
|
|
||||||
plate_type = "Green Plate"
|
|
||||||
else:
|
|
||||||
color = (0, 255, 255) # 黄色 (BGR格式)
|
|
||||||
plate_type = "Unknown"
|
|
||||||
|
|
||||||
# 绘制边界框
|
|
||||||
cv2.rectangle(result_image, (x1, y1), (x2, y2), color, 2)
|
|
||||||
|
|
||||||
# 绘制置信度标签
|
|
||||||
label = f"{plate_type}: {confidence:.2f}"
|
|
||||||
label_size = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)[0]
|
|
||||||
|
|
||||||
# 绘制标签背景
|
|
||||||
cv2.rectangle(result_image,
|
|
||||||
(x1, y1 - label_size[1] - 10),
|
|
||||||
(x1 + label_size[0], y1),
|
|
||||||
color, -1)
|
|
||||||
|
|
||||||
# 绘制标签文字
|
|
||||||
cv2.putText(result_image, label,
|
|
||||||
(x1, y1 - 5),
|
|
||||||
cv2.FONT_HERSHEY_SIMPLEX, 0.6,
|
|
||||||
(255, 255, 255), 2)
|
|
||||||
|
|
||||||
return result_image
|
|
||||||
|
|
||||||
def crop_plates(self, image: np.ndarray, detections: List[dict]) -> List[np.ndarray]:
|
|
||||||
"""
|
|
||||||
切割车牌图像
|
|
||||||
|
|
||||||
Args:
|
|
||||||
image: 原始图像
|
|
||||||
detections: 检测结果列表
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
切割后的车牌图像列表
|
|
||||||
"""
|
|
||||||
plate_images = []
|
|
||||||
|
|
||||||
for detection in detections:
|
|
||||||
bbox = detection['bbox']
|
|
||||||
x1, y1, x2, y2 = bbox
|
|
||||||
|
|
||||||
# 确保坐标有效
|
|
||||||
if x2 > x1 and y2 > y1:
|
|
||||||
# 切割车牌区域
|
|
||||||
plate_image = image[y1:y2, x1:x2]
|
|
||||||
if plate_image.size > 0:
|
|
||||||
plate_images.append(plate_image)
|
|
||||||
|
|
||||||
return plate_images
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# 车牌检测系统依赖包
|
|
||||||
|
|
||||||
# 核心依赖
|
|
||||||
PyQt5>=5.15.0
|
|
||||||
opencv-python>=4.5.0
|
|
||||||
onnxruntime-gpu>=1.12.0
|
|
||||||
numpy>=1.21.0
|
|
||||||
|
|
||||||
# 可选依赖(车牌识别)
|
|
||||||
# paddlepaddle>=2.4.0
|
|
||||||
# paddleocr>=2.6.0
|
|
||||||
# pytesseract>=0.3.10
|
|
||||||
|
|
||||||
# 开发依赖
|
|
||||||
# pytest>=7.0.0
|
|
||||||
# black>=22.0.0
|
|
||||||
# flake8>=4.0.0
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# UI模块初始化文件
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,348 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
主界面窗口
|
|
||||||
包含视频显示区域、控制按钮和车牌号显示区域
|
|
||||||
"""
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import os
|
|
||||||
from PyQt5.QtWidgets import (
|
|
||||||
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
||||||
QLabel, QPushButton, QFrame, QTextEdit, QGroupBox,
|
|
||||||
QCheckBox, QSpinBox, QSlider, QGridLayout
|
|
||||||
)
|
|
||||||
from PyQt5.QtCore import Qt, QTimer, pyqtSignal
|
|
||||||
from PyQt5.QtGui import QFont, QPixmap, QPalette, QImage
|
|
||||||
|
|
||||||
from .video_widget import VideoWidget
|
|
||||||
from utils.video_capture import VideoCapture
|
|
||||||
from models.yolo_detector import YOLODetector
|
|
||||||
from models.plate_recognizer import PlateRecognizerManager
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
|
||||||
"""主窗口类"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.video_capture = None
|
|
||||||
self.yolo_detector = None
|
|
||||||
self.plate_recognizer = PlateRecognizerManager('mock') # 车牌识别管理器
|
|
||||||
self.timer = QTimer()
|
|
||||||
self.use_camera = 1 # 1: 摄像头, 0: 视频文件
|
|
||||||
self.detected_plates = [] # 存储切割后的车牌图像数组
|
|
||||||
self.current_frame = None # 存储当前帧用于车牌矫正
|
|
||||||
|
|
||||||
self.init_ui()
|
|
||||||
self.init_detector()
|
|
||||||
self.init_video_capture()
|
|
||||||
self.connect_signals()
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
"""初始化用户界面"""
|
|
||||||
self.setWindowTitle("车牌检测系统 - YOLO11s")
|
|
||||||
self.setGeometry(100, 100, 1200, 800)
|
|
||||||
|
|
||||||
# 创建中央widget
|
|
||||||
central_widget = QWidget()
|
|
||||||
self.setCentralWidget(central_widget)
|
|
||||||
|
|
||||||
# 主布局
|
|
||||||
main_layout = QHBoxLayout(central_widget)
|
|
||||||
|
|
||||||
# 左侧视频显示区域
|
|
||||||
self.create_video_area(main_layout)
|
|
||||||
|
|
||||||
# 右侧控制和信息显示区域
|
|
||||||
self.create_control_area(main_layout)
|
|
||||||
|
|
||||||
# 设置布局比例
|
|
||||||
main_layout.setStretch(0, 3) # 视频区域占3/4
|
|
||||||
main_layout.setStretch(1, 1) # 控制区域占1/4
|
|
||||||
|
|
||||||
def create_video_area(self, parent_layout):
|
|
||||||
"""创建视频显示区域"""
|
|
||||||
video_frame = QFrame()
|
|
||||||
video_frame.setFrameStyle(QFrame.StyledPanel)
|
|
||||||
video_layout = QVBoxLayout(video_frame)
|
|
||||||
|
|
||||||
# 视频显示widget
|
|
||||||
self.video_widget = VideoWidget()
|
|
||||||
video_layout.addWidget(self.video_widget)
|
|
||||||
|
|
||||||
parent_layout.addWidget(video_frame)
|
|
||||||
|
|
||||||
def create_control_area(self, parent_layout):
|
|
||||||
"""创建控制和信息显示区域"""
|
|
||||||
control_frame = QFrame()
|
|
||||||
control_frame.setFrameStyle(QFrame.StyledPanel)
|
|
||||||
control_frame.setMaximumWidth(300)
|
|
||||||
control_layout = QVBoxLayout(control_frame)
|
|
||||||
|
|
||||||
# 控制按钮组
|
|
||||||
self.create_control_buttons(control_layout)
|
|
||||||
|
|
||||||
# 检测信息显示
|
|
||||||
self.create_detection_info(control_layout)
|
|
||||||
|
|
||||||
# 车牌号显示区域
|
|
||||||
self.create_plate_display(control_layout)
|
|
||||||
|
|
||||||
# 系统状态显示
|
|
||||||
self.create_status_display(control_layout)
|
|
||||||
|
|
||||||
parent_layout.addWidget(control_frame)
|
|
||||||
|
|
||||||
def create_control_buttons(self, parent_layout):
|
|
||||||
"""创建控制按钮"""
|
|
||||||
button_group = QGroupBox("控制面板")
|
|
||||||
button_layout = QVBoxLayout(button_group)
|
|
||||||
|
|
||||||
# 开始/停止按钮
|
|
||||||
self.start_btn = QPushButton("开始检测")
|
|
||||||
self.start_btn.setMinimumHeight(40)
|
|
||||||
self.start_btn.clicked.connect(self.toggle_detection)
|
|
||||||
button_layout.addWidget(self.start_btn)
|
|
||||||
|
|
||||||
# 视频源切换
|
|
||||||
self.camera_checkbox = QCheckBox("使用摄像头")
|
|
||||||
self.camera_checkbox.setChecked(True)
|
|
||||||
self.camera_checkbox.stateChanged.connect(self.toggle_video_source)
|
|
||||||
button_layout.addWidget(self.camera_checkbox)
|
|
||||||
|
|
||||||
# 检测开关
|
|
||||||
self.detection_checkbox = QCheckBox("启用检测")
|
|
||||||
self.detection_checkbox.setChecked(True)
|
|
||||||
button_layout.addWidget(self.detection_checkbox)
|
|
||||||
|
|
||||||
parent_layout.addWidget(button_group)
|
|
||||||
|
|
||||||
def create_detection_info(self, parent_layout):
|
|
||||||
"""创建检测信息显示"""
|
|
||||||
info_group = QGroupBox("检测信息")
|
|
||||||
info_layout = QVBoxLayout(info_group)
|
|
||||||
|
|
||||||
# FPS显示
|
|
||||||
self.fps_label = QLabel("FPS: 0")
|
|
||||||
self.fps_label.setFont(QFont("Arial", 12, QFont.Bold))
|
|
||||||
info_layout.addWidget(self.fps_label)
|
|
||||||
|
|
||||||
# 检测数量
|
|
||||||
self.detection_count_label = QLabel("检测到车牌: 0")
|
|
||||||
info_layout.addWidget(self.detection_count_label)
|
|
||||||
|
|
||||||
# 模型信息
|
|
||||||
self.model_info_label = QLabel("模型: YOLO11s (ONNX)")
|
|
||||||
info_layout.addWidget(self.model_info_label)
|
|
||||||
|
|
||||||
parent_layout.addWidget(info_group)
|
|
||||||
|
|
||||||
def create_plate_display(self, parent_layout):
|
|
||||||
"""创建车牌号显示区域"""
|
|
||||||
plate_group = QGroupBox("车牌识别结果")
|
|
||||||
plate_layout = QVBoxLayout(plate_group)
|
|
||||||
|
|
||||||
# 当前识别的车牌号
|
|
||||||
self.current_plate_label = QLabel("当前车牌: 未识别")
|
|
||||||
self.current_plate_label.setFont(QFont("Arial", 14, QFont.Bold))
|
|
||||||
self.current_plate_label.setStyleSheet("color: blue; padding: 10px; border: 1px solid gray;")
|
|
||||||
plate_layout.addWidget(self.current_plate_label)
|
|
||||||
|
|
||||||
# 矫正后的车牌图像显示
|
|
||||||
self.plate_image_label = QLabel("矫正后车牌图像")
|
|
||||||
self.plate_image_label.setAlignment(Qt.AlignCenter)
|
|
||||||
self.plate_image_label.setMinimumHeight(100)
|
|
||||||
self.plate_image_label.setMaximumHeight(150)
|
|
||||||
self.plate_image_label.setStyleSheet("border: 1px solid gray; background-color: #f0f0f0;")
|
|
||||||
plate_layout.addWidget(self.plate_image_label)
|
|
||||||
|
|
||||||
# 历史车牌记录
|
|
||||||
history_label = QLabel("历史记录:")
|
|
||||||
plate_layout.addWidget(history_label)
|
|
||||||
|
|
||||||
self.plate_history = QTextEdit()
|
|
||||||
self.plate_history.setMaximumHeight(150)
|
|
||||||
self.plate_history.setReadOnly(True)
|
|
||||||
plate_layout.addWidget(self.plate_history)
|
|
||||||
|
|
||||||
# 预留接口说明
|
|
||||||
interface_label = QLabel("注: 车牌识别接口已预留,可接入OCR模型")
|
|
||||||
interface_label.setStyleSheet("color: gray; font-size: 10px;")
|
|
||||||
plate_layout.addWidget(interface_label)
|
|
||||||
|
|
||||||
parent_layout.addWidget(plate_group)
|
|
||||||
|
|
||||||
def create_status_display(self, parent_layout):
|
|
||||||
"""创建系统状态显示"""
|
|
||||||
status_group = QGroupBox("系统状态")
|
|
||||||
status_layout = QVBoxLayout(status_group)
|
|
||||||
|
|
||||||
self.status_label = QLabel("状态: 就绪")
|
|
||||||
status_layout.addWidget(self.status_label)
|
|
||||||
|
|
||||||
self.gpu_status_label = QLabel("GPU: 检测中...")
|
|
||||||
status_layout.addWidget(self.gpu_status_label)
|
|
||||||
|
|
||||||
parent_layout.addWidget(status_group)
|
|
||||||
|
|
||||||
# 添加弹性空间
|
|
||||||
parent_layout.addStretch()
|
|
||||||
|
|
||||||
def init_detector(self):
|
|
||||||
"""初始化YOLO检测器"""
|
|
||||||
try:
|
|
||||||
model_path = os.path.join(os.path.dirname(__file__), "..", "yolo11sth50.onnx")
|
|
||||||
self.yolo_detector = YOLODetector(model_path)
|
|
||||||
self.model_info_label.setText(f"模型: YOLO11s (ONNX) - GPU: {self.yolo_detector.use_gpu}")
|
|
||||||
self.gpu_status_label.setText(f"GPU: {'启用' if self.yolo_detector.use_gpu else '禁用'}")
|
|
||||||
except Exception as e:
|
|
||||||
self.status_label.setText(f"模型加载失败: {str(e)}")
|
|
||||||
|
|
||||||
def init_video_capture(self):
|
|
||||||
"""初始化视频捕获"""
|
|
||||||
try:
|
|
||||||
self.video_capture = VideoCapture()
|
|
||||||
self.status_label.setText("视频捕获初始化成功")
|
|
||||||
except Exception as e:
|
|
||||||
self.status_label.setText(f"视频捕获初始化失败: {str(e)}")
|
|
||||||
|
|
||||||
def connect_signals(self):
|
|
||||||
"""连接信号和槽"""
|
|
||||||
self.timer.timeout.connect(self.update_frame)
|
|
||||||
|
|
||||||
def toggle_detection(self):
|
|
||||||
"""切换检测状态"""
|
|
||||||
if self.timer.isActive():
|
|
||||||
self.stop_detection()
|
|
||||||
else:
|
|
||||||
self.start_detection()
|
|
||||||
|
|
||||||
def start_detection(self):
|
|
||||||
"""开始检测"""
|
|
||||||
if self.video_capture and self.video_capture.start_capture(self.use_camera):
|
|
||||||
# 根据视频源类型设置定时器间隔
|
|
||||||
video_fps = self.video_capture.get_video_fps()
|
|
||||||
timer_interval = int(1000 / video_fps) # 转换为毫秒
|
|
||||||
self.timer.start(timer_interval)
|
|
||||||
|
|
||||||
self.start_btn.setText("停止检测")
|
|
||||||
source_type = "摄像头" if self.use_camera else f"视频文件({video_fps:.1f}FPS)"
|
|
||||||
self.status_label.setText(f"检测中... - {source_type}")
|
|
||||||
else:
|
|
||||||
self.status_label.setText("启动失败")
|
|
||||||
|
|
||||||
def stop_detection(self):
|
|
||||||
"""停止检测"""
|
|
||||||
self.timer.stop()
|
|
||||||
if self.video_capture:
|
|
||||||
self.video_capture.stop_capture()
|
|
||||||
self.start_btn.setText("开始检测")
|
|
||||||
self.status_label.setText("已停止")
|
|
||||||
|
|
||||||
def toggle_video_source(self, state):
|
|
||||||
"""切换视频源"""
|
|
||||||
self.use_camera = 1 if state == Qt.Checked else 0
|
|
||||||
if self.timer.isActive():
|
|
||||||
self.stop_detection()
|
|
||||||
self.start_detection()
|
|
||||||
|
|
||||||
def update_frame(self):
|
|
||||||
"""更新帧"""
|
|
||||||
if not self.video_capture:
|
|
||||||
return
|
|
||||||
|
|
||||||
frame, fps = self.video_capture.get_frame()
|
|
||||||
if frame is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 保存当前帧用于车牌矫正
|
|
||||||
self.current_frame = frame.copy()
|
|
||||||
|
|
||||||
# 更新FPS显示
|
|
||||||
self.fps_label.setText(f"FPS: {fps:.1f}")
|
|
||||||
|
|
||||||
# 进行检测
|
|
||||||
if self.detection_checkbox.isChecked() and self.yolo_detector:
|
|
||||||
detections = self.yolo_detector.detect(frame)
|
|
||||||
frame = self.yolo_detector.draw_detections(frame, detections)
|
|
||||||
|
|
||||||
# 切割车牌图像
|
|
||||||
if detections:
|
|
||||||
self.detected_plates = self.yolo_detector.crop_plates(frame, detections)
|
|
||||||
|
|
||||||
# 统计不同类型车牌数量
|
|
||||||
blue_count = sum(1 for d in detections if d['class_id'] == 0)
|
|
||||||
green_count = sum(1 for d in detections if d['class_id'] == 1)
|
|
||||||
total_count = len(detections)
|
|
||||||
|
|
||||||
self.detection_count_label.setText(f"检测到车牌: {total_count} (蓝牌:{blue_count}, 绿牌:{green_count})")
|
|
||||||
|
|
||||||
# 调用车牌识别接口(预留)
|
|
||||||
self.recognize_plates(self.detected_plates, detections)
|
|
||||||
else:
|
|
||||||
self.detection_count_label.setText("检测到车牌: 0")
|
|
||||||
|
|
||||||
# 显示帧
|
|
||||||
self.video_widget.update_frame(frame)
|
|
||||||
|
|
||||||
def recognize_plates(self, plate_images, detections):
|
|
||||||
"""车牌识别接口(预留)"""
|
|
||||||
# 这里是预留的车牌识别接口
|
|
||||||
# 可以接入OCR模型进行车牌号识别
|
|
||||||
if plate_images and detections and self.current_frame is not None:
|
|
||||||
# 获取最新检测到的车牌信息
|
|
||||||
latest_detection = detections[-1] # 取最后一个检测结果
|
|
||||||
plate_type = "Blue Plate" if latest_detection['class_id'] == 0 else "Green Plate"
|
|
||||||
confidence = latest_detection['confidence']
|
|
||||||
|
|
||||||
# 处理蓝色车牌的矫正
|
|
||||||
corrected_image = None
|
|
||||||
if latest_detection['class_id'] == 0: # 蓝色车牌
|
|
||||||
try:
|
|
||||||
bbox = latest_detection['bbox']
|
|
||||||
corrected_image = self.plate_recognizer.preprocess_blue_plate(
|
|
||||||
plate_images[-1], self.current_frame, bbox
|
|
||||||
)
|
|
||||||
self._display_plate_image(corrected_image)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"蓝色车牌矫正失败: {e}")
|
|
||||||
self.plate_image_label.setText("蓝色车牌矫正失败")
|
|
||||||
elif latest_detection['class_id'] == 1: # 绿色车牌
|
|
||||||
# 绿色车牌处理预留
|
|
||||||
self.plate_image_label.setText("绿色车牌处理\n(待实现)")
|
|
||||||
|
|
||||||
# 模拟识别结果
|
|
||||||
plate_text = f"Mock {plate_type}-{len(plate_images)}"
|
|
||||||
self.current_plate_label.setText(f"Current Plate: {plate_text} (Confidence: {confidence:.2f})")
|
|
||||||
|
|
||||||
# 添加到历史记录
|
|
||||||
import datetime
|
|
||||||
timestamp = datetime.datetime.now().strftime("%H:%M:%S")
|
|
||||||
self.plate_history.append(f"[{timestamp}] {plate_text} (Confidence: {confidence:.2f})")
|
|
||||||
|
|
||||||
def _display_plate_image(self, image):
|
|
||||||
"""在界面上显示车牌图像"""
|
|
||||||
try:
|
|
||||||
# 将OpenCV图像转换为QPixmap
|
|
||||||
if len(image.shape) == 3:
|
|
||||||
height, width, channel = image.shape
|
|
||||||
bytes_per_line = 3 * width
|
|
||||||
q_image = QImage(image.data, width, height, bytes_per_line, QImage.Format_RGB888).rgbSwapped()
|
|
||||||
else:
|
|
||||||
height, width = image.shape
|
|
||||||
bytes_per_line = width
|
|
||||||
q_image = QImage(image.data, width, height, bytes_per_line, QImage.Format_Grayscale8)
|
|
||||||
|
|
||||||
# 缩放图像以适应标签大小
|
|
||||||
pixmap = QPixmap.fromImage(q_image)
|
|
||||||
scaled_pixmap = pixmap.scaled(self.plate_image_label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
|
|
||||||
|
|
||||||
self.plate_image_label.setPixmap(scaled_pixmap)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"显示车牌图像失败: {e}")
|
|
||||||
self.plate_image_label.setText(f"图像显示失败: {str(e)}")
|
|
||||||
|
|
||||||
def closeEvent(self, event):
|
|
||||||
"""窗口关闭事件"""
|
|
||||||
self.stop_detection()
|
|
||||||
event.accept()
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
视频显示组件
|
|
||||||
用于显示视频帧和检测结果
|
|
||||||
"""
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import numpy as np
|
|
||||||
from PyQt5.QtWidgets import QLabel
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
from PyQt5.QtGui import QImage, QPixmap, QPainter, QPen, QFont
|
|
||||||
|
|
||||||
class VideoWidget(QLabel):
|
|
||||||
"""视频显示组件"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.setMinimumSize(640, 480)
|
|
||||||
self.setStyleSheet("border: 1px solid gray; background-color: black;")
|
|
||||||
self.setAlignment(Qt.AlignCenter)
|
|
||||||
self.setText("视频显示区域\n点击'开始检测'开始")
|
|
||||||
self.setScaledContents(True)
|
|
||||||
|
|
||||||
def update_frame(self, frame):
|
|
||||||
"""更新显示帧"""
|
|
||||||
if frame is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 转换BGR到RGB
|
|
||||||
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
|
||||||
h, w, ch = rgb_frame.shape
|
|
||||||
bytes_per_line = ch * w
|
|
||||||
|
|
||||||
# 创建QImage
|
|
||||||
qt_image = QImage(rgb_frame.data, w, h, bytes_per_line, QImage.Format_RGB888)
|
|
||||||
|
|
||||||
# 转换为QPixmap并显示
|
|
||||||
pixmap = QPixmap.fromImage(qt_image)
|
|
||||||
|
|
||||||
# 缩放以适应widget大小,保持宽高比
|
|
||||||
scaled_pixmap = pixmap.scaled(
|
|
||||||
self.size(),
|
|
||||||
Qt.KeepAspectRatio,
|
|
||||||
Qt.SmoothTransformation
|
|
||||||
)
|
|
||||||
|
|
||||||
self.setPixmap(scaled_pixmap)
|
|
||||||
|
|
||||||
def paintEvent(self, event):
|
|
||||||
"""绘制事件"""
|
|
||||||
super().paintEvent(event)
|
|
||||||
|
|
||||||
# 如果没有图像,显示提示文本
|
|
||||||
if not self.pixmap():
|
|
||||||
painter = QPainter(self)
|
|
||||||
painter.setPen(QPen(Qt.white))
|
|
||||||
painter.setFont(QFont("Arial", 16))
|
|
||||||
painter.drawText(self.rect(), Qt.AlignCenter, "视频显示区域\n点击'开始检测'开始")
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# 工具模块初始化文件
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,280 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
"""
|
|
||||||
视频捕获管理
|
|
||||||
支持摄像头和视频文件的切换和管理
|
|
||||||
"""
|
|
||||||
|
|
||||||
import cv2
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import threading
|
|
||||||
from typing import Optional, Tuple
|
|
||||||
|
|
||||||
class VideoCapture:
|
|
||||||
"""视频捕获管理类"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
"""
|
|
||||||
初始化视频捕获管理器
|
|
||||||
"""
|
|
||||||
self.cap = None
|
|
||||||
self.is_camera = True
|
|
||||||
self.video_path = None
|
|
||||||
self.fps_counter = FPSCounter()
|
|
||||||
self.frame_lock = threading.Lock()
|
|
||||||
self.current_frame = None
|
|
||||||
self.is_running = False
|
|
||||||
self.video_fps = 30.0 # 视频原始帧率
|
|
||||||
|
|
||||||
# 设置视频文件路径
|
|
||||||
self.video_file_path = os.path.join(os.path.dirname(__file__), "..", "video.mp4")
|
|
||||||
|
|
||||||
def start_capture(self, use_camera: int = 1) -> bool:
|
|
||||||
"""
|
|
||||||
开始视频捕获
|
|
||||||
|
|
||||||
Args:
|
|
||||||
use_camera: 1使用摄像头,0使用视频文件
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
是否成功启动
|
|
||||||
"""
|
|
||||||
self.stop_capture()
|
|
||||||
|
|
||||||
self.is_camera = bool(use_camera)
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.is_camera:
|
|
||||||
# 使用摄像头
|
|
||||||
self.cap = cv2.VideoCapture(0)
|
|
||||||
if not self.cap.isOpened():
|
|
||||||
# 尝试其他摄像头索引
|
|
||||||
for i in range(1, 5):
|
|
||||||
self.cap = cv2.VideoCapture(i)
|
|
||||||
if self.cap.isOpened():
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print("无法打开摄像头")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 设置摄像头参数
|
|
||||||
self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
|
|
||||||
self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
|
|
||||||
self.cap.set(cv2.CAP_PROP_FPS, 30)
|
|
||||||
|
|
||||||
print("摄像头启动成功")
|
|
||||||
|
|
||||||
else:
|
|
||||||
# 使用视频文件
|
|
||||||
if not os.path.exists(self.video_file_path):
|
|
||||||
print(f"视频文件不存在: {self.video_file_path}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.cap = cv2.VideoCapture(self.video_file_path)
|
|
||||||
if not self.cap.isOpened():
|
|
||||||
print(f"无法打开视频文件: {self.video_file_path}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# 获取视频原始帧率
|
|
||||||
self.video_fps = self.cap.get(cv2.CAP_PROP_FPS)
|
|
||||||
if self.video_fps <= 0:
|
|
||||||
self.video_fps = 25.0 # 默认帧率
|
|
||||||
|
|
||||||
print(f"视频文件加载成功: {self.video_file_path}, FPS: {self.video_fps}")
|
|
||||||
|
|
||||||
self.is_running = True
|
|
||||||
self.fps_counter.reset()
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"启动视频捕获失败: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def stop_capture(self):
|
|
||||||
"""停止视频捕获"""
|
|
||||||
self.is_running = False
|
|
||||||
|
|
||||||
if self.cap is not None:
|
|
||||||
self.cap.release()
|
|
||||||
self.cap = None
|
|
||||||
|
|
||||||
with self.frame_lock:
|
|
||||||
self.current_frame = None
|
|
||||||
|
|
||||||
print("视频捕获已停止")
|
|
||||||
|
|
||||||
def get_frame(self) -> Tuple[Optional[cv2.Mat], float]:
|
|
||||||
"""
|
|
||||||
获取当前帧
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
(frame, fps): 当前帧和FPS
|
|
||||||
"""
|
|
||||||
if not self.is_running or self.cap is None:
|
|
||||||
return None, 0.0
|
|
||||||
|
|
||||||
try:
|
|
||||||
ret, frame = self.cap.read()
|
|
||||||
|
|
||||||
if not ret:
|
|
||||||
if not self.is_camera:
|
|
||||||
# 视频文件播放完毕,重新开始(循环播放)
|
|
||||||
self.cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
|
|
||||||
ret, frame = self.cap.read()
|
|
||||||
|
|
||||||
if not ret:
|
|
||||||
return None, 0.0
|
|
||||||
|
|
||||||
# 更新FPS计数器
|
|
||||||
fps = self.fps_counter.update()
|
|
||||||
|
|
||||||
# 在帧上绘制FPS信息
|
|
||||||
frame_with_fps = self._draw_fps(frame, fps)
|
|
||||||
|
|
||||||
with self.frame_lock:
|
|
||||||
self.current_frame = frame_with_fps.copy()
|
|
||||||
|
|
||||||
return frame_with_fps, fps
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"获取帧失败: {e}")
|
|
||||||
return None, 0.0
|
|
||||||
|
|
||||||
def _draw_fps(self, frame: cv2.Mat, fps: float) -> cv2.Mat:
|
|
||||||
"""
|
|
||||||
在帧上绘制FPS信息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
frame: 输入帧
|
|
||||||
fps: 当前FPS
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
绘制了FPS的帧
|
|
||||||
"""
|
|
||||||
result_frame = frame.copy()
|
|
||||||
|
|
||||||
# FPS文本
|
|
||||||
fps_text = f"FPS: {fps:.1f}"
|
|
||||||
|
|
||||||
# 文本参数
|
|
||||||
font = cv2.FONT_HERSHEY_SIMPLEX
|
|
||||||
font_scale = 0.7
|
|
||||||
color = (0, 255, 0) # 绿色
|
|
||||||
thickness = 2
|
|
||||||
|
|
||||||
# 获取文本尺寸
|
|
||||||
text_size = cv2.getTextSize(fps_text, font, font_scale, thickness)[0]
|
|
||||||
|
|
||||||
# 绘制背景矩形
|
|
||||||
cv2.rectangle(result_frame,
|
|
||||||
(10, 10),
|
|
||||||
(20 + text_size[0], 20 + text_size[1]),
|
|
||||||
(0, 0, 0), -1)
|
|
||||||
|
|
||||||
# 绘制FPS文本
|
|
||||||
cv2.putText(result_frame, fps_text,
|
|
||||||
(15, 15 + text_size[1]),
|
|
||||||
font, font_scale, color, thickness)
|
|
||||||
|
|
||||||
return result_frame
|
|
||||||
|
|
||||||
def get_capture_info(self) -> dict:
|
|
||||||
"""
|
|
||||||
获取捕获信息
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
捕获信息字典
|
|
||||||
"""
|
|
||||||
info = {
|
|
||||||
'is_running': self.is_running,
|
|
||||||
'is_camera': self.is_camera,
|
|
||||||
'video_path': self.video_file_path if not self.is_camera else None,
|
|
||||||
'fps': self.fps_counter.get_fps(),
|
|
||||||
'video_fps': self.video_fps
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.cap is not None:
|
|
||||||
try:
|
|
||||||
info['width'] = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
|
||||||
info['height'] = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
|
||||||
if not self.is_camera:
|
|
||||||
info['total_frames'] = int(self.cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
|
||||||
info['current_frame'] = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return info
|
|
||||||
|
|
||||||
def get_video_fps(self) -> float:
|
|
||||||
"""
|
|
||||||
获取视频帧率
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
视频帧率,摄像头返回30.0,视频文件返回原始帧率
|
|
||||||
"""
|
|
||||||
if self.is_camera:
|
|
||||||
return 30.0 # 摄像头固定30FPS
|
|
||||||
else:
|
|
||||||
return self.video_fps # 视频文件原始帧率
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
"""析构函数"""
|
|
||||||
self.stop_capture()
|
|
||||||
|
|
||||||
class FPSCounter:
|
|
||||||
"""FPS计数器"""
|
|
||||||
|
|
||||||
def __init__(self, window_size: int = 30):
|
|
||||||
"""
|
|
||||||
初始化FPS计数器
|
|
||||||
|
|
||||||
Args:
|
|
||||||
window_size: 滑动窗口大小
|
|
||||||
"""
|
|
||||||
self.window_size = window_size
|
|
||||||
self.frame_times = []
|
|
||||||
self.last_time = time.time()
|
|
||||||
|
|
||||||
def update(self) -> float:
|
|
||||||
"""
|
|
||||||
更新FPS计数
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
当前FPS
|
|
||||||
"""
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
# 添加当前帧时间
|
|
||||||
self.frame_times.append(current_time)
|
|
||||||
|
|
||||||
# 保持窗口大小
|
|
||||||
if len(self.frame_times) > self.window_size:
|
|
||||||
self.frame_times.pop(0)
|
|
||||||
|
|
||||||
# 计算FPS
|
|
||||||
if len(self.frame_times) >= 2:
|
|
||||||
time_diff = self.frame_times[-1] - self.frame_times[0]
|
|
||||||
if time_diff > 0:
|
|
||||||
fps = (len(self.frame_times) - 1) / time_diff
|
|
||||||
return fps
|
|
||||||
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
def get_fps(self) -> float:
|
|
||||||
"""
|
|
||||||
获取当前FPS
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
当前FPS
|
|
||||||
"""
|
|
||||||
if len(self.frame_times) >= 2:
|
|
||||||
time_diff = self.frame_times[-1] - self.frame_times[0]
|
|
||||||
if time_diff > 0:
|
|
||||||
return (len(self.frame_times) - 1) / time_diff
|
|
||||||
return 0.0
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
"""重置计数器"""
|
|
||||||
self.frame_times.clear()
|
|
||||||
self.last_time = time.time()
|
|
||||||
Binary file not shown.
Reference in New Issue
Block a user