yincheng.zhong
2025-11-22 820749d41d8bc0fdfeb1f10283a2ba3b426e60f2
增加DSP功能,但是FPU还有问题
已添加30个文件
已修改11个文件
22537 ■■■■ 文件已修改
.cursor/rules/workflow3.mdc 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/.mxproject 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/APL/UDPClient.c 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/Core/Inc/FreeRTOSConfig.h 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/MDK-ARM/STM32H743.uvguix.zhyin 442 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/MDK-ARM/STM32H743.uvprojx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/MDK-ARM/STM32H743/STM32H743.lnp 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/MDK-ARM/STM32H743/STM32H743.map 6669 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/MDK-ARM/STM32H743/STM32H743_STM32H743.dep 4121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/MDK-ARM/startup_stm32h743xx.lst 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/Middlewares/ST/ARM/DSP/Inc/arm_math.h 8970 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/Middlewares/Third_Party/ARM/DSP/LICENSE.txt 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
STM32H743/STM32H743.ioc 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/__pycache__/gps_imu_receiver.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/__pycache__/gps_imu_receiver.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/gecaolujing2.txt 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/gui_state.json 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/README.md 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/__init__.py 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/__pycache__/dynamics.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/__pycache__/geo.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/__pycache__/protocols.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/__pycache__/simulator.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/dynamics.py 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/geo.py 171 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/protocols.py 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/hitl/simulator.py 303 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/realtime_control.py 841 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__init__.py 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__pycache__/conftest.cpython-310-pytest-7.1.2.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__pycache__/test_dynamics.cpython-310-pytest-7.1.2.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__pycache__/test_geo.cpython-310-pytest-7.1.2.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__pycache__/test_protocols.cpython-310-pytest-7.1.2.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/__pycache__/test_simulator.cpython-310-pytest-7.1.2.pyc 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/conftest.py 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/test_dynamics.py 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/test_geo.py 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/test_protocols.py 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
python/tests_hitl/test_simulator.py 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.cursor/rules/workflow3.mdc
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
---
alwaysApply: true
---
# AI助手核心规则
## ä¸‰é˜¶æ®µå·¥ä½œæµ
### é˜¶æ®µä¸€ï¼šåˆ†æžé—®é¢˜
**声明格式**:`【分析问题】`
**目的**
因为可能存在多个可选方案,要做出正确的决策,需要足够的依据。
**必须做的事**:
- ç†è§£æˆ‘的意图,如果有歧义请问我
- æœç´¢æ‰€æœ‰ç›¸å…³ä»£ç 
- è¯†åˆ«é—®é¢˜æ ¹å› 
**主动发现问题**
- å‘现重复代码
- è¯†åˆ«ä¸åˆç†çš„命名
- å‘现多余的代码、类
- å‘现可能过时的设计
- å‘现过于复杂的设计、调用
- å‘现不一致的类型定义
- è¿›ä¸€æ­¥æœç´¢ä»£ç ï¼Œçœ‹æ˜¯å¦æ›´å¤§èŒƒå›´å†…有类似问题
做完以上事项,就可以向我提问了。
**绝对禁止**:
- âŒ ä¿®æ”¹ä»»ä½•代码
- âŒ æ€¥äºŽç»™å‡ºè§£å†³æ–¹æ¡ˆ
- âŒ è·³è¿‡æœç´¢å’Œç†è§£æ­¥éª¤
- âŒ ä¸åˆ†æžå°±æŽ¨èæ–¹æ¡ˆ
**阶段转换规则**
本阶段你要向我提问。
如果存在多个你无法抉择的方案,要问我,作为提问的一部分。
如果没有需要问我的,则直接进入下一阶段。
### é˜¶æ®µäºŒï¼šåˆ¶å®šæ–¹æ¡ˆ
**声明格式**:`【制定方案】`
**前置条件**:
- æˆ‘明确回答了关键技术决策。
**必须做的事**:
- åˆ—出变更(新增、修改、删除)的文件,简要描述每个文件的变化
- æ¶ˆé™¤é‡å¤é€»è¾‘:如果发现重复代码,必须通过复用或抽象来消除
- ç¡®ä¿ä¿®æ”¹åŽçš„代码符合DRY原则和良好的架构设计
如果新发现了向我收集的关键决策,在这个阶段你还可以继续问我,直到没有不明确的问题之后,本阶段结束。
本阶段不允许自动切换到下一阶段。
### é˜¶æ®µä¸‰ï¼šæ‰§è¡Œæ–¹æ¡ˆ
**声明格式**:`【执行方案】`
**必须做的事**:
- ä¸¥æ ¼æŒ‰ç…§é€‰å®šæ–¹æ¡ˆå®žçް
- ä¿®æ”¹åŽè¿è¡Œç±»åž‹æ£€æŸ¥
**绝对禁止**:
- âŒ æäº¤ä»£ç ï¼ˆé™¤éžç”¨æˆ·æ˜Žç¡®è¦æ±‚)
- å¯åŠ¨å¼€å‘æœåŠ¡å™¨
如果在这个阶段发现了拿不准的问题,请向我提问。
收到用户消息时,一般从【分析问题】阶段开始,除非用户明确指定阶段的名字。
STM32H743/.mxproject
@@ -1,5 +1,5 @@
[PreviousLibFiles]
LibFiles=Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_bus.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_crs.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_system.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_utils.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dmamux.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_mdma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_def.h;Drivers\STM32H7xx_HAL_Driver\Inc\Legacy\stm32_hal_legacy.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_usart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_lpuart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart_ex.h;Middlewares\Third_Party\FreeRTOS\Source\include\croutine.h;Middlewares\Third_Party\FreeRTOS\Source\include\deprecated_definitions.h;Middlewares\Third_Party\FreeRTOS\Source\include\event_groups.h;Middlewares\Third_Party\FreeRTOS\Source\include\FreeRTOS.h;Middlewares\Third_Party\FreeRTOS\Source\include\list.h;Middlewares\Third_Party\FreeRTOS\Source\include\message_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_prototypes.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_wrappers.h;Middlewares\Third_Party\FreeRTOS\Source\include\portable.h;Middlewares\Third_Party\FreeRTOS\Source\include\projdefs.h;Middlewares\Third_Party\FreeRTOS\Source\include\queue.h;Middlewares\Third_Party\FreeRTOS\Source\include\semphr.h;Middlewares\Third_Party\FreeRTOS\Source\include\stack_macros.h;Middlewares\Third_Party\FreeRTOS\Source\include\StackMacros.h;Middlewares\Third_Party\FreeRTOS\Source\include\stream_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\task.h;Middlewares\Third_Party\FreeRTOS\Source\include\timers.h;Middlewares\Third_Party\FreeRTOS\Source\include\atomic.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_mpool.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_os2.h;Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\portmacro.h;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_exti.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart_ex.c;Middlewares\Third_Party\FreeRTOS\Source\croutine.c;Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;Middlewares\Third_Party\FreeRTOS\Source\list.c;Middlewares\Third_Party\FreeRTOS\Source\queue.c;Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;Middlewares\Third_Party\FreeRTOS\Source\tasks.c;Middlewares\Third_Party\FreeRTOS\Source\timers.c;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\port.c;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_bus.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_crs.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_system.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_utils.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dmamux.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_mdma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_def.h;Drivers\STM32H7xx_HAL_Driver\Inc\Legacy\stm32_hal_legacy.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_usart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_lpuart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart_ex.h;Middlewares\Third_Party\FreeRTOS\Source\include\croutine.h;Middlewares\Third_Party\FreeRTOS\Source\include\deprecated_definitions.h;Middlewares\Third_Party\FreeRTOS\Source\include\event_groups.h;Middlewares\Third_Party\FreeRTOS\Source\include\FreeRTOS.h;Middlewares\Third_Party\FreeRTOS\Source\include\list.h;Middlewares\Third_Party\FreeRTOS\Source\include\message_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_prototypes.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_wrappers.h;Middlewares\Third_Party\FreeRTOS\Source\include\portable.h;Middlewares\Third_Party\FreeRTOS\Source\include\projdefs.h;Middlewares\Third_Party\FreeRTOS\Source\include\queue.h;Middlewares\Third_Party\FreeRTOS\Source\include\semphr.h;Middlewares\Third_Party\FreeRTOS\Source\include\stack_macros.h;Middlewares\Third_Party\FreeRTOS\Source\include\StackMacros.h;Middlewares\Third_Party\FreeRTOS\Source\include\stream_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\task.h;Middlewares\Third_Party\FreeRTOS\Source\include\timers.h;Middlewares\Third_Party\FreeRTOS\Source\include\atomic.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_mpool.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_os2.h;Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\portmacro.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\stm32h743xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\stm32h7xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\system_stm32h7xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\system_stm32h7xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates\system_stm32h7xx.c;
LibFiles=Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_bus.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_crs.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_system.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_utils.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dmamux.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_mdma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_def.h;Drivers\STM32H7xx_HAL_Driver\Inc\Legacy\stm32_hal_legacy.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_usart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_lpuart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart_ex.h;Middlewares\Third_Party\FreeRTOS\Source\include\croutine.h;Middlewares\Third_Party\FreeRTOS\Source\include\deprecated_definitions.h;Middlewares\Third_Party\FreeRTOS\Source\include\event_groups.h;Middlewares\Third_Party\FreeRTOS\Source\include\FreeRTOS.h;Middlewares\Third_Party\FreeRTOS\Source\include\list.h;Middlewares\Third_Party\FreeRTOS\Source\include\message_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_prototypes.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_wrappers.h;Middlewares\Third_Party\FreeRTOS\Source\include\portable.h;Middlewares\Third_Party\FreeRTOS\Source\include\projdefs.h;Middlewares\Third_Party\FreeRTOS\Source\include\queue.h;Middlewares\Third_Party\FreeRTOS\Source\include\semphr.h;Middlewares\Third_Party\FreeRTOS\Source\include\stack_macros.h;Middlewares\Third_Party\FreeRTOS\Source\include\StackMacros.h;Middlewares\Third_Party\FreeRTOS\Source\include\stream_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\task.h;Middlewares\Third_Party\FreeRTOS\Source\include\timers.h;Middlewares\Third_Party\FreeRTOS\Source\include\atomic.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_mpool.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_os2.h;Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\portmacro.h;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c_ex.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_exti.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart.c;Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart_ex.c;Middlewares\Third_Party\FreeRTOS\Source\croutine.c;Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;Middlewares\Third_Party\FreeRTOS\Source\list.c;Middlewares\Third_Party\FreeRTOS\Source\queue.c;Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;Middlewares\Third_Party\FreeRTOS\Source\tasks.c;Middlewares\Third_Party\FreeRTOS\Source\timers.c;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\port.c;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_tim_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_cortex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_rcc_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_bus.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_rcc.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_crs.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_system.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_utils.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_flash_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_gpio_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_gpio.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_hsem.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_dma_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_dmamux.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_mdma.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_pwr_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_pwr.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_def.h;Drivers\STM32H7xx_HAL_Driver\Inc\Legacy\stm32_hal_legacy.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_i2c_ex.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_exti.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_tim.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_usart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_ll_lpuart.h;Drivers\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_uart_ex.h;Middlewares\Third_Party\FreeRTOS\Source\include\croutine.h;Middlewares\Third_Party\FreeRTOS\Source\include\deprecated_definitions.h;Middlewares\Third_Party\FreeRTOS\Source\include\event_groups.h;Middlewares\Third_Party\FreeRTOS\Source\include\FreeRTOS.h;Middlewares\Third_Party\FreeRTOS\Source\include\list.h;Middlewares\Third_Party\FreeRTOS\Source\include\message_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_prototypes.h;Middlewares\Third_Party\FreeRTOS\Source\include\mpu_wrappers.h;Middlewares\Third_Party\FreeRTOS\Source\include\portable.h;Middlewares\Third_Party\FreeRTOS\Source\include\projdefs.h;Middlewares\Third_Party\FreeRTOS\Source\include\queue.h;Middlewares\Third_Party\FreeRTOS\Source\include\semphr.h;Middlewares\Third_Party\FreeRTOS\Source\include\stack_macros.h;Middlewares\Third_Party\FreeRTOS\Source\include\StackMacros.h;Middlewares\Third_Party\FreeRTOS\Source\include\stream_buffer.h;Middlewares\Third_Party\FreeRTOS\Source\include\task.h;Middlewares\Third_Party\FreeRTOS\Source\include\timers.h;Middlewares\Third_Party\FreeRTOS\Source\include\atomic.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_mpool.h;Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\freertos_os2.h;Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\portmacro.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\stm32h743xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\stm32h7xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\system_stm32h7xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Include\system_stm32h7xx.h;Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates\system_stm32h7xx.c;Drivers\CMSIS\Include\cmsis_armcc.h;Drivers\CMSIS\Include\cmsis_armclang.h;Drivers\CMSIS\Include\cmsis_armclang_ltm.h;Drivers\CMSIS\Include\cmsis_compiler.h;Drivers\CMSIS\Include\cmsis_gcc.h;Drivers\CMSIS\Include\cmsis_iccarm.h;Drivers\CMSIS\Include\cmsis_version.h;Drivers\CMSIS\Include\core_armv81mml.h;Drivers\CMSIS\Include\core_armv8mbl.h;Drivers\CMSIS\Include\core_armv8mml.h;Drivers\CMSIS\Include\core_cm0.h;Drivers\CMSIS\Include\core_cm0plus.h;Drivers\CMSIS\Include\core_cm1.h;Drivers\CMSIS\Include\core_cm23.h;Drivers\CMSIS\Include\core_cm3.h;Drivers\CMSIS\Include\core_cm33.h;Drivers\CMSIS\Include\core_cm35p.h;Drivers\CMSIS\Include\core_cm4.h;Drivers\CMSIS\Include\core_cm7.h;Drivers\CMSIS\Include\core_sc000.h;Drivers\CMSIS\Include\core_sc300.h;Drivers\CMSIS\Include\mpu_armv7.h;Drivers\CMSIS\Include\mpu_armv8.h;Drivers\CMSIS\Include\tz_context.h;
[PreviousUsedKeilFiles]
SourceFiles=..\Core\Src\main.c;..\Core\Src\freertos.c;..\Core\Src\stm32h7xx_it.c;..\Core\Src\stm32h7xx_hal_msp.c;..\Core\Src\stm32h7xx_hal_timebase_tim.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_exti.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart_ex.c;..\Middlewares\Third_Party\FreeRTOS\Source\croutine.c;..\Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;..\Middlewares\Third_Party\FreeRTOS\Source\list.c;..\Middlewares\Third_Party\FreeRTOS\Source\queue.c;..\Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;..\Middlewares\Third_Party\FreeRTOS\Source\tasks.c;..\Middlewares\Third_Party\FreeRTOS\Source\timers.c;..\Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;..\Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;..\Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\port.c;..\Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates\system_stm32h7xx.c;..\Core\Src\system_stm32h7xx.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_tim_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_cortex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_rcc_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_flash_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_gpio.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_hsem.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_dma_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_mdma.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_pwr_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_i2c_ex.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_exti.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart.c;..\Drivers\STM32H7xx_HAL_Driver\Src\stm32h7xx_hal_uart_ex.c;..\Middlewares\Third_Party\FreeRTOS\Source\croutine.c;..\Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;..\Middlewares\Third_Party\FreeRTOS\Source\list.c;..\Middlewares\Third_Party\FreeRTOS\Source\queue.c;..\Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;..\Middlewares\Third_Party\FreeRTOS\Source\tasks.c;..\Middlewares\Third_Party\FreeRTOS\Source\timers.c;..\Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;..\Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;..\Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\port.c;..\Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates\system_stm32h7xx.c;..\Core\Src\system_stm32h7xx.c;;;..\Middlewares\Third_Party\FreeRTOS\Source\croutine.c;..\Middlewares\Third_Party\FreeRTOS\Source\event_groups.c;..\Middlewares\Third_Party\FreeRTOS\Source\list.c;..\Middlewares\Third_Party\FreeRTOS\Source\queue.c;..\Middlewares\Third_Party\FreeRTOS\Source\stream_buffer.c;..\Middlewares\Third_Party\FreeRTOS\Source\tasks.c;..\Middlewares\Third_Party\FreeRTOS\Source\timers.c;..\Middlewares\Third_Party\FreeRTOS\Source\CMSIS_RTOS_V2\cmsis_os2.c;..\Middlewares\Third_Party\FreeRTOS\Source\portable\MemMang\heap_4.c;..\Middlewares\Third_Party\FreeRTOS\Source\portable\RVDS\ARM_CM4F\port.c;
STM32H743/APL/UDPClient.c
@@ -20,7 +20,7 @@
#include "AppConfig.h"
#include "DBG.h"
#include "EG800FSM.h"
#include "arm_math.h"
/*******************************************************************************
 *                                  Macro                                      *
 *******************************************************************************/
STM32H743/Core/Inc/FreeRTOSConfig.h
@@ -68,7 +68,7 @@
#define configTICK_RATE_HZ                       ((TickType_t)1000)
#define configMAX_PRIORITIES                     ( 56 )
#define configMINIMAL_STACK_SIZE                 ((uint16_t)128)
#define configTOTAL_HEAP_SIZE                    ((size_t)32768)  /* å¢žåŠ åˆ°32KB,原15KB可能不够 */
#define configTOTAL_HEAP_SIZE                    ((size_t)15360)
#define configMAX_TASK_NAME_LEN                  ( 16 )
#define configUSE_TRACE_FACILITY                 1
#define configUSE_16_BIT_TICKS                   0
STM32H743/MDK-ARM/STM32H743.uvguix.zhyin
@@ -93,8 +93,8 @@
      <flags>2</flags>
      <showCmd>3</showCmd>
      <MinPosition>
        <xPos>-1</xPos>
        <yPos>-1</yPos>
        <xPos>-32000</xPos>
        <yPos>-32000</yPos>
      </MinPosition>
      <MaxPosition>
        <xPos>-1</xPos>
@@ -110,8 +110,8 @@
    <MDIClientArea>
      <RegID>0</RegID>
      <MDITabState>
        <Len>4012</Len>
        <Data>0100000004000000010000000100000001000000010000000000000002000000000000000100000001000000000000002800000028000000010000002A000000080000000100000047453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C496E7465726E65745C4D6F64756C655C45473830305C454738303046534D2E63000000000A454738303046534D2E6300000000C5D4F200FFFFFFFF40453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D444B2D41524D5C737461727475705F73746D33326837343378782E730000000015737461727475705F73746D33326837343378782E7300000000FFDC7800FFFFFFFF52453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D6964646C6577617265735C54686972645F50617274795C4672656552544F535C536F757263655C7461736B732E6300000000077461736B732E6300000000BECEA100FFFFFFFF2C453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C6170702E6300000000056170702E6300000000F0A0A100FFFFFFFF5B453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D6964646C6577617265735C54686972645F50617274795C4672656552544F535C536F757263655C696E636C7564655C73656D7068722E68000000000873656D7068722E6800000000BCA8E100FFFFFFFF38453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C496E7465726E65745C536F636B65742E630000000008536F636B65742E63000000009CC1B600FFFFFFFF4A453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C496E7465726E65745C4D6F64756C655C45473830305C45473830304472697665722E63000000000D45473830304472697665722E6300000000F7B88600FFFFFFFF2C453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C4442472E6300000000054442472E6300000000D9ADC200FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C554450436C69656E742E63000000000B554450436C69656E742E6300000000A5C2D700FFFFFFFF2D453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C48414C5C556172742E630000000006556172742E6300000000B3A6BE00FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C436F72655C5372635C6D61696E2E6300000000066D61696E2E6300000000EAD6A300FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C626C7565746F6F74682E63000000000B626C7565746F6F74682E6300000000F6FA7D00FFFFFFFF35453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C676C6F62616C5F706172616D2E63000000000E676C6F62616C5F706172616D2E6300000000B5E99D00FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C417070436F6E6669672E68000000000B417070436F6E6669672E68000000005FC3CF00FFFFFFFF31453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C48414C5C4D4355466C6173682E63000000000A4D4355466C6173682E6300000000C1838300FFFFFFFF3A453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C436F72655C5372635C73746D3332683778785F69742E63000000000E73746D3332683778785F69742E6300000000CACAD500FFFFFFFF35453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C676C6F62616C5F706172616D2E68000000000E676C6F62616C5F706172616D2E6800000000C5D4F200FFFFFFFF59453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C447269766572735C53544D3332483778785F48414C5F4472697665725C5372635C73746D3332683778785F68616C5F666C6173682E63000000001573746D3332683778785F68616C5F666C6173682E6300000000FFDC7800FFFFFFFF59453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C447269766572735C53544D3332483778785F48414C5F4472697665725C496E635C73746D3332683778785F68616C5F666C6173682E68000000001573746D3332683778785F68616C5F666C6173682E6800000000BECEA100FFFFFFFF31453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C4E545249504170702E63000000000A4E545249504170702E6300000000F0A0A100FFFFFFFF2E453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C5368656C6C2E6300000000075368656C6C2E6300000000BCA8E100FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C544350436C69656E742E63000000000B544350436C69656E742E63000000009CC1B600FFFFFFFF43453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4849444F4C6962726172795C496E636C7564655C4849444F5F5368656C6C2E68000000000C4849444F5F5368656C6C2E6800000000F7B88600FFFFFFFF24443A5C4B65696C5F76355C41524D5C41524D43435C696E636C7564655C737464696F2E680000000007737464696F2E6800000000D9ADC200FFFFFFFF2C453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C4750532E6300000000054750532E6300000000A5C2D700FFFFFFFF2D453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C48414C5C556172742E680000000006556172742E6800000000B3A6BE00FFFFFFFF58453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C447269766572735C53544D3332483778785F48414C5F4472697665725C5372635C73746D3332683778785F68616C5F756172742E63000000001473746D3332683778785F68616C5F756172742E6300000000EAD6A300FFFFFFFF3F453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C436F72655C5372635C73746D3332683778785F68616C5F6D73702E63000000001373746D3332683778785F68616C5F6D73702E6300000000F6FA7D00FFFFFFFF3A453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C41504C5C73657269616C5F61745F636D645F6170702E63000000001373657269616C5F61745F636D645F6170702E6300000000B5E99D00FFFFFFFF2D453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C534255532E630000000006534255532E63000000005FC3CF00FFFFFFFF45453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4849444F4C6962726172795C496E636C7564655C4849444F5F545970654465662E68000000000E4849444F5F545970654465662E6800000000C1838300FFFFFFFF64453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D6964646C6577617265735C54686972645F50617274795C4672656552544F535C536F757263655C434D5349535F52544F535F56325C636D7369735F6F73322E63000000000B636D7369735F6F73322E6300000000CACAD500FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C70776D5F6374726F6C2E63000000000B70776D5F6374726F6C2E6300000000C5D4F200FFFFFFFF42453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4849444F4C6962726172795C496E636C7564655C4849444F5F5574696C2E68000000000B4849444F5F5574696C2E6800000000FFDC7800FFFFFFFF32453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C70776D5F6374726F6C2E68000000000B70776D5F6374726F6C2E6800000000BECEA100FFFFFFFF33453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C507974686F6E4C696E6B2E63000000000C507974686F6E4C696E6B2E6300000000F0A0A100FFFFFFFF49453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4849444F4C6962726172795C496E636C7564655C4849444F5F4D6F6462757343524331362E6800000000124849444F5F4D6F6462757343524331362E6800000000BCA8E100FFFFFFFF33453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C507974686F6E4C696E6B2E68000000000C507974686F6E4C696E6B2E68000000009CC1B600FFFFFFFF5D453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D6964646C6577617265735C54686972645F50617274795C4672656552544F535C536F757263655C696E636C7564655C4672656552544F532E68000000000A4672656552544F532E6800000000F7B88600FFFFFFFF2C453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C464D4C5C4750532E6800000000054750532E6800000000D9ADC200FFFFFFFF64453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D6964646C6577617265735C54686972645F50617274795C4672656552544F535C536F757263655C434D5349535F52544F535F56325C636D7369735F6F73322E68000000000B636D7369735F6F73322E6800000000A5C2D700FFFFFFFF6D453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C4D6964646C6577617265735C54686972645F50617274795C4672656552544F535C536F757263655C706F727461626C655C525644535C41524D5F434D34465C706F72746D6163726F2E68000000000B706F72746D6163726F2E6800000000B3A6BE00FFFFFFFF0100000010000000C5D4F200FFDC7800BECEA100F0A0A100BCA8E1009CC1B600F7B88600D9ADC200A5C2D700B3A6BE00EAD6A300F6FA7D00B5E99D005FC3CF00C1838300CACAD500010000000000000002000000670100006600000000060000C0010000</Data>
        <Len>365</Len>
        <Data>0100000004000000010000000100000001000000010000000000000002000000000000000100000001000000000000002800000028000000010000000200000001000000010000003E453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C436F72655C5372635C73797374656D5F73746D3332683778782E63000000001273797374656D5F73746D3332683778782E6300000000F6FA7D00FFFFFFFF43453A5C4749545C4C61776E6D6F7765725F53544D333248375C53544D3332483734335C447269766572735C434D5349535C496E636C7564655C636F72655F636D372E68000000000A636F72655F636D372E6800000000FFDC7800FFFFFFFF0100000010000000C5D4F200FFDC7800BECEA100F0A0A100BCA8E1009CC1B600F7B88600D9ADC200A5C2D700B3A6BE00EAD6A300F6FA7D00B5E99D005FC3CF00C1838300CACAD5000100000000000000020000006701000066000000000600005D020000</Data>
      </MDITabState>
    </MDIClientArea>
    <ViewEx>
@@ -150,7 +150,7 @@
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000660000006001000090010000</Data>
          <Data>0300000066000000600100002D020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
@@ -170,7 +170,7 @@
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000660000006001000090010000</Data>
          <Data>0300000066000000600100002D020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
@@ -450,7 +450,7 @@
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000660000006001000090010000</Data>
          <Data>0300000066000000600100002D020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
@@ -470,7 +470,7 @@
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000660000006001000090010000</Data>
          <Data>0300000066000000600100002D020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
@@ -490,11 +490,11 @@
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000C4010000FD050000F5020000</Data>
          <Data>0300000061020000FD050000F5020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
          <Data>8A000000A1000000C20200000F010000</Data>
          <Data>D80000009A020000D8060000FB030000</Data>
        </RectRecentFloat>
      </Window>
      <Window>
@@ -526,15 +526,15 @@
        <IsActivated>0</IsActivated>
        <MRUWidth>32767</MRUWidth>
        <PinState>0</PinState>
        <RecentFrameAlignment>4096</RecentFrameAlignment>
        <RecentFrameAlignment>32768</RecentFrameAlignment>
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000C4010000FD050000F5020000</Data>
          <Data>0300000061020000FD050000F5020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
          <Data>8A000000A1000000C20200000F010000</Data>
          <Data>D80000009A020000D8060000FB030000</Data>
        </RectRecentFloat>
      </Window>
      <Window>
@@ -1150,7 +1150,7 @@
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000660000006001000068020000</Data>
          <Data>03000000660000006001000090010000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
@@ -1166,15 +1166,15 @@
        <IsActivated>0</IsActivated>
        <MRUWidth>32767</MRUWidth>
        <PinState>0</PinState>
        <RecentFrameAlignment>4096</RecentFrameAlignment>
        <RecentFrameAlignment>32768</RecentFrameAlignment>
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000C4010000FD050000F5020000</Data>
          <Data>0300000061020000FD050000F5020000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
          <Data>8A000000A1000000C20200000F010000</Data>
          <Data>D80000009A020000D8060000FB030000</Data>
        </RectRecentFloat>
      </Window>
      <Window>
@@ -1186,15 +1186,15 @@
        <IsActivated>0</IsActivated>
        <MRUWidth>32767</MRUWidth>
        <PinState>0</PinState>
        <RecentFrameAlignment>4096</RecentFrameAlignment>
        <RecentFrameAlignment>32768</RecentFrameAlignment>
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000C40100007D070000F5020000</Data>
          <Data>0300000061020000FD05000092030000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
          <Data>8A000000A10000006D0100005D020000</Data>
          <Data>D80000009A020000D8060000FB030000</Data>
        </RectRecentFloat>
      </Window>
      <Window>
@@ -1246,15 +1246,15 @@
        <IsActivated>0</IsActivated>
        <MRUWidth>32767</MRUWidth>
        <PinState>0</PinState>
        <RecentFrameAlignment>4096</RecentFrameAlignment>
        <RecentFrameAlignment>32768</RecentFrameAlignment>
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000C40100007D070000F5020000</Data>
          <Data>0300000061020000FD05000092030000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
          <Data>8A000000A10000006D0100005D020000</Data>
          <Data>D80000009A020000D8060000FB030000</Data>
        </RectRecentFloat>
      </Window>
      <Window>
@@ -1266,15 +1266,15 @@
        <IsActivated>0</IsActivated>
        <MRUWidth>32767</MRUWidth>
        <PinState>0</PinState>
        <RecentFrameAlignment>4096</RecentFrameAlignment>
        <RecentFrameAlignment>32768</RecentFrameAlignment>
        <RecentRowIndex>0</RecentRowIndex>
        <RectRecentDocked>
          <Len>16</Len>
          <Data>03000000C40100007D070000F5020000</Data>
          <Data>0300000061020000FD05000092030000</Data>
        </RectRecentDocked>
        <RectRecentFloat>
          <Len>16</Len>
          <Data>8A000000A10000006D0100005D020000</Data>
          <Data>D80000009A020000D8060000FB030000</Data>
        </RectRecentFloat>
      </Window>
      <Window>
@@ -1798,15 +1798,15 @@
        </RectRecentFloat>
      </Window>
      <DockMan>
        <Len>3312</Len>
        <Data>000000000B000000000000000020000000000000FFFFFFFFFFFFFFFFC4000000BD00000070040000C1000000000000000100000004000000010000000000000000000000FFFFFFFF08000000CB00000057010000CC000000F08B00005A01000079070000D601000045890000FFFF02000B004354616262656450616E650020000000000000C40000006600000070040000D4000000C40000004F00000070040000BD0000000000000040280046080000000B446973617373656D626C7900000000CB00000001000000FFFFFFFFFFFFFFFF14506572666F726D616E636520416E616C797A6572000000005701000001000000FFFFFFFFFFFFFFFF14506572666F726D616E636520416E616C797A657200000000CC00000001000000FFFFFFFFFFFFFFFF0E4C6F67696320416E616C797A657200000000F08B000001000000FFFFFFFFFFFFFFFF0D436F646520436F766572616765000000005A01000001000000FFFFFFFFFFFFFFFF11496E737472756374696F6E205472616365000000007907000001000000FFFFFFFFFFFFFFFF0F53797374656D20416E616C797A657200000000D601000001000000FFFFFFFFFFFFFFFF104576656E742053746174697374696373000000004589000001000000FFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000001000000FFFFFFFFCB00000001000000FFFFFFFFCB000000000000000040000000000000FFFFFFFFFFFFFFFFAC0300004F000000B0030000A5010000000000000200000004000000010000000000000000000000FFFFFFFF2B000000E2050000CA0900002D8C00002E8C00002F8C0000308C0000318C0000328C0000338C0000348C0000358C0000368C0000378C0000388C0000398C00003A8C00003B8C00003C8C00003D8C00003E8C00003F8C0000408C0000418C000050C3000051C3000052C3000053C3000054C3000055C3000056C3000057C3000058C3000059C300005AC300005BC300005CC300005DC300005EC300005FC3000060C3000061C3000062C3000063C3000001800040000000000000B00300006600000070040000BC010000B00300004F00000070040000A501000000000000404100462B0000000753796D626F6C7300000000E205000001000000FFFFFFFFFFFFFFFF0A5472616365204461746100000000CA09000001000000FFFFFFFFFFFFFFFF00000000002D8C000001000000FFFFFFFFFFFFFFFF00000000002E8C000001000000FFFFFFFFFFFFFFFF00000000002F8C000001000000FFFFFFFFFFFFFFFF0000000000308C000001000000FFFFFFFFFFFFFFFF0000000000318C000001000000FFFFFFFFFFFFFFFF0000000000328C000001000000FFFFFFFFFFFFFFFF0000000000338C000001000000FFFFFFFFFFFFFFFF0000000000348C000001000000FFFFFFFFFFFFFFFF0000000000358C000001000000FFFFFFFFFFFFFFFF0000000000368C000001000000FFFFFFFFFFFFFFFF0000000000378C000001000000FFFFFFFFFFFFFFFF0000000000388C000001000000FFFFFFFFFFFFFFFF0000000000398C000001000000FFFFFFFFFFFFFFFF00000000003A8C000001000000FFFFFFFFFFFFFFFF00000000003B8C000001000000FFFFFFFFFFFFFFFF00000000003C8C000001000000FFFFFFFFFFFFFFFF00000000003D8C000001000000FFFFFFFFFFFFFFFF00000000003E8C000001000000FFFFFFFFFFFFFFFF00000000003F8C000001000000FFFFFFFFFFFFFFFF0000000000408C000001000000FFFFFFFFFFFFFFFF0000000000418C000001000000FFFFFFFFFFFFFFFF000000000050C3000001000000FFFFFFFFFFFFFFFF000000000051C3000001000000FFFFFFFFFFFFFFFF000000000052C3000001000000FFFFFFFFFFFFFFFF000000000053C3000001000000FFFFFFFFFFFFFFFF000000000054C3000001000000FFFFFFFFFFFFFFFF000000000055C3000001000000FFFFFFFFFFFFFFFF000000000056C3000001000000FFFFFFFFFFFFFFFF000000000057C3000001000000FFFFFFFFFFFFFFFF000000000058C3000001000000FFFFFFFFFFFFFFFF000000000059C3000001000000FFFFFFFFFFFFFFFF00000000005AC3000001000000FFFFFFFFFFFFFFFF00000000005BC3000001000000FFFFFFFFFFFFFFFF00000000005CC3000001000000FFFFFFFFFFFFFFFF00000000005DC3000001000000FFFFFFFFFFFFFFFF00000000005EC3000001000000FFFFFFFFFFFFFFFF00000000005FC3000001000000FFFFFFFFFFFFFFFF000000000060C3000001000000FFFFFFFFFFFFFFFF000000000061C3000001000000FFFFFFFFFFFFFFFF000000000062C3000001000000FFFFFFFFFFFFFFFF000000000063C3000001000000FFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000001000000FFFFFFFFE205000001000000FFFFFFFFE2050000000000000010000001000000FFFFFFFFFFFFFFFF630100004F00000067010000A901000001000000020000100400000001000000CAFEFFFF3F060000FFFFFFFF05000000ED0300006D000000C3000000C4000000739400000180001000000100000000000000660000006301000098020000000000004F00000063010000A90100000000000040410056050000000750726F6A65637401000000ED03000001000000FFFFFFFFFFFFFFFF05426F6F6B73010000006D00000001000000FFFFFFFFFFFFFFFF0946756E6374696F6E7301000000C300000001000000FFFFFFFFFFFFFFFF0954656D706C6174657301000000C400000001000000FFFFFFFFFFFFFFFF09526567697374657273000000007394000001000000FFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000000001000000FFFFFFFFED03000001000000FFFFFFFFED030000000000000080000000000000FFFFFFFFFFFFFFFF0000000091010000700400009501000000000000010000000400000001000000000000000000000000000000000000000000000001000000C6000000FFFFFFFF0F0000008F070000930700009407000095070000960700009007000091070000B5010000B801000038030000B9050000BA050000BB050000BC050000CB0900000180008000000000000000000000AC010000700400002E0200000000000095010000700400001702000000000000404100460F0000001343616C6C20537461636B202B204C6F63616C73000000008F07000001000000FFFFFFFFFFFFFFFF0755415254202331000000009307000001000000FFFFFFFFFFFFFFFF0755415254202332000000009407000001000000FFFFFFFFFFFFFFFF0755415254202333000000009507000001000000FFFFFFFFFFFFFFFF15446562756720287072696E74662920566965776572000000009607000001000000FFFFFFFFFFFFFFFF0757617463682031000000009007000001000000FFFFFFFFFFFFFFFF0757617463682032000000009107000001000000FFFFFFFFFFFFFFFF10547261636520457863657074696F6E7300000000B501000001000000FFFFFFFFFFFFFFFF0E4576656E7420436F756E7465727300000000B801000001000000FFFFFFFFFFFFFFFF09554C494E4B706C7573000000003803000001000000FFFFFFFFFFFFFFFF084D656D6F7279203100000000B905000001000000FFFFFFFFFFFFFFFF084D656D6F7279203200000000BA05000001000000FFFFFFFFFFFFFFFF084D656D6F7279203300000000BB05000001000000FFFFFFFFFFFFFFFF084D656D6F7279203400000000BC05000001000000FFFFFFFFFFFFFFFF105472616365204E617669676174696F6E00000000CB09000001000000FFFFFFFFFFFFFFFFFFFFFFFF0000000001000000000000000000000001000000FFFFFFFF38020000950100003C0200001702000000000000020000000400000000000000000000000000000000000000000000000000000002000000C6000000FFFFFFFF8F07000001000000FFFFFFFF8F07000001000000C6000000000000000080000001000000FFFFFFFFFFFFFFFF00000000A901000000060000AD0100000100000001000010040000000100000076FDFFFF02010000FFFFFFFF06000000C5000000C7000000B4010000D2010000CF0100007794000001800080000001000000000000009C02000080070000FD03000000000000AD010000000600000E0300000000000040820056060000000C4275696C64204F757470757401000000C500000001000000FFFFFFFFFFFFFFFF0D46696E6420496E2046696C657301000000C700000001000000FFFFFFFFFFFFFFFF0A4572726F72204C69737400000000B401000001000000FFFFFFFFFFFFFFFF0E536F757263652042726F7773657200000000D201000001000000FFFFFFFFFFFFFFFF0E416C6C205265666572656E63657300000000CF01000001000000FFFFFFFFFFFFFFFF0742726F77736572010000007794000001000000FFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000000001000000FFFFFFFFC500000001000000FFFFFFFFC5000000000000000000000000000000</Data>
        <Len>3332</Len>
        <Data>000000000B000000000000000020000000000000FFFFFFFFFFFFFFFFC4000000BD00000070040000C1000000000000000100000004000000010000000000000000000000FFFFFFFF08000000CB00000057010000CC000000F08B00005A01000079070000D601000045890000FFFF02000B004354616262656450616E650020000000000000C40000006600000070040000D4000000C40000004F00000070040000BD0000000000000040280046080000000B446973617373656D626C7900000000CB00000001000000FFFFFFFFFFFFFFFF14506572666F726D616E636520416E616C797A6572000000005701000001000000FFFFFFFFFFFFFFFF14506572666F726D616E636520416E616C797A657200000000CC00000001000000FFFFFFFFFFFFFFFF0E4C6F67696320416E616C797A657200000000F08B000001000000FFFFFFFFFFFFFFFF0D436F646520436F766572616765000000005A01000001000000FFFFFFFFFFFFFFFF11496E737472756374696F6E205472616365000000007907000001000000FFFFFFFFFFFFFFFF0F53797374656D20416E616C797A657200000000D601000001000000FFFFFFFFFFFFFFFF104576656E742053746174697374696373000000004589000001000000FFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000001000000FFFFFFFFCB00000001000000FFFFFFFFCB000000000000000040000000000000FFFFFFFFFFFFFFFFAC0300004F000000B0030000A5010000000000000200000004000000010000000000000000000000FFFFFFFF2B000000E2050000CA0900002D8C00002E8C00002F8C0000308C0000318C0000328C0000338C0000348C0000358C0000368C0000378C0000388C0000398C00003A8C00003B8C00003C8C00003D8C00003E8C00003F8C0000408C0000418C000050C3000051C3000052C3000053C3000054C3000055C3000056C3000057C3000058C3000059C300005AC300005BC300005CC300005DC300005EC300005FC3000060C3000061C3000062C3000063C3000001800040000000000000B00300006600000070040000BC010000B00300004F00000070040000A501000000000000404100462B0000000753796D626F6C7300000000E205000001000000FFFFFFFFFFFFFFFF0A5472616365204461746100000000CA09000001000000FFFFFFFFFFFFFFFF00000000002D8C000001000000FFFFFFFFFFFFFFFF00000000002E8C000001000000FFFFFFFFFFFFFFFF00000000002F8C000001000000FFFFFFFFFFFFFFFF0000000000308C000001000000FFFFFFFFFFFFFFFF0000000000318C000001000000FFFFFFFFFFFFFFFF0000000000328C000001000000FFFFFFFFFFFFFFFF0000000000338C000001000000FFFFFFFFFFFFFFFF0000000000348C000001000000FFFFFFFFFFFFFFFF0000000000358C000001000000FFFFFFFFFFFFFFFF0000000000368C000001000000FFFFFFFFFFFFFFFF0000000000378C000001000000FFFFFFFFFFFFFFFF0000000000388C000001000000FFFFFFFFFFFFFFFF0000000000398C000001000000FFFFFFFFFFFFFFFF00000000003A8C000001000000FFFFFFFFFFFFFFFF00000000003B8C000001000000FFFFFFFFFFFFFFFF00000000003C8C000001000000FFFFFFFFFFFFFFFF00000000003D8C000001000000FFFFFFFFFFFFFFFF00000000003E8C000001000000FFFFFFFFFFFFFFFF00000000003F8C000001000000FFFFFFFFFFFFFFFF0000000000408C000001000000FFFFFFFFFFFFFFFF0000000000418C000001000000FFFFFFFFFFFFFFFF000000000050C3000001000000FFFFFFFFFFFFFFFF000000000051C3000001000000FFFFFFFFFFFFFFFF000000000052C3000001000000FFFFFFFFFFFFFFFF000000000053C3000001000000FFFFFFFFFFFFFFFF000000000054C3000001000000FFFFFFFFFFFFFFFF000000000055C3000001000000FFFFFFFFFFFFFFFF000000000056C3000001000000FFFFFFFFFFFFFFFF000000000057C3000001000000FFFFFFFFFFFFFFFF000000000058C3000001000000FFFFFFFFFFFFFFFF000000000059C3000001000000FFFFFFFFFFFFFFFF00000000005AC3000001000000FFFFFFFFFFFFFFFF00000000005BC3000001000000FFFFFFFFFFFFFFFF00000000005CC3000001000000FFFFFFFFFFFFFFFF00000000005DC3000001000000FFFFFFFFFFFFFFFF00000000005EC3000001000000FFFFFFFFFFFFFFFF00000000005FC3000001000000FFFFFFFFFFFFFFFF000000000060C3000001000000FFFFFFFFFFFFFFFF000000000061C3000001000000FFFFFFFFFFFFFFFF000000000062C3000001000000FFFFFFFFFFFFFFFF000000000063C3000001000000FFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000000000000001000000FFFFFFFFE205000001000000FFFFFFFFE2050000000000000010000001000000FFFFFFFFFFFFFFFF630100004F000000670100004602000001000000020000100400000001000000CAFEFFFF3F060000FFFFFFFF05000000ED0300006D000000C3000000C40000007394000001800010000001000000000000006600000063010000C0010000000000004F00000063010000460200000000000040410056050000000750726F6A65637401000000ED03000001000000FFFFFFFFFFFFFFFF05426F6F6B73010000006D00000001000000FFFFFFFFFFFFFFFF0946756E6374696F6E7301000000C300000001000000FFFFFFFFFFFFFFFF0954656D706C6174657301000000C400000001000000FFFFFFFFFFFFFFFF09526567697374657273000000007394000001000000FFFFFFFFFFFFFFFF00000000000000000000000000000000000000000000000001000000FFFFFFFFED03000001000000FFFFFFFFED030000000000000080000000000000FFFFFFFFFFFFFFFF0000000091010000700400009501000000000000010000000400000001000000000000000000000000000000000000000000000001000000C6000000FFFFFFFF0F0000008F070000930700009407000095070000960700009007000091070000B5010000B801000038030000B9050000BA050000BB050000BC050000CB0900000180008000000000000000000000AC010000700400002E0200000000000095010000700400001702000000000000404100460F0000001343616C6C20537461636B202B204C6F63616C73000000008F07000001000000FFFFFFFFFFFFFFFF0755415254202331000000009307000001000000FFFFFFFFFFFFFFFF0755415254202332000000009407000001000000FFFFFFFFFFFFFFFF0755415254202333000000009507000001000000FFFFFFFFFFFFFFFF15446562756720287072696E74662920566965776572000000009607000001000000FFFFFFFFFFFFFFFF0757617463682031000000009007000001000000FFFFFFFFFFFFFFFF0757617463682032000000009107000001000000FFFFFFFFFFFFFFFF10547261636520457863657074696F6E7300000000B501000001000000FFFFFFFFFFFFFFFF0E4576656E7420436F756E7465727300000000B801000001000000FFFFFFFFFFFFFFFF09554C494E4B706C7573000000003803000001000000FFFFFFFFFFFFFFFF084D656D6F7279203100000000B905000001000000FFFFFFFFFFFFFFFF084D656D6F7279203200000000BA05000001000000FFFFFFFFFFFFFFFF084D656D6F7279203300000000BB05000001000000FFFFFFFFFFFFFFFF084D656D6F7279203400000000BC05000001000000FFFFFFFFFFFFFFFF105472616365204E617669676174696F6E00000000CB09000001000000FFFFFFFFFFFFFFFFFFFFFFFF0000000001000000000000000000000001000000FFFFFFFF38020000950100003C0200001702000000000000020000000400000000000000000000000000000000000000000000000000000002000000C6000000FFFFFFFF8F07000001000000FFFFFFFF8F07000001000000C6000000000000000080000001000000FFFFFFFFFFFFFFFF0000000046020000000600004A0200000100000001000010040000000100000001FEFFFFB500000000000000000000000000000001000000FFFFFFFF06000000C5000000C7000000B4010000D2010000CF0100007794000001800080000001000000D80000009A020000D8060000FB030000000000004A020000000600000E0300000000000040820056060000000C4275696C64204F757470757401000000C500000001000000FFFFFFFFFFFFFFFF0D46696E6420496E2046696C657301000000C700000001000000FFFFFFFFFFFFFFFF0A4572726F72204C69737400000000B401000001000000FFFFFFFFFFFFFFFF0E536F757263652042726F7773657200000000D201000001000000FFFFFFFFFFFFFFFF0E416C6C205265666572656E63657300000000CF01000001000000FFFFFFFFFFFFFFFF0742726F77736572010000007794000001000000FFFFFFFFFFFFFFFF0000000000000000000000000000000000000000000000000000000001000000FFFFFFFFC500000001000000FFFFFFFFC5000000000000000000000000000000</Data>
      </DockMan>
      <ToolBar>
        <RegID>59392</RegID>
        <Name>File</Name>
        <Buttons>
          <Len>2623</Len>
          <Data>00200000010000002800FFFF01001100434D4643546F6F6C426172427574746F6E00E100000000000000000000000000000000000000000000000100000001000000018001E100000000000001000000000000000000000000000000000100000001000000018003E1000000000000020000000000000000000000000000000001000000010000000180CD7F0000000000000300000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000018023E100000000040004000000000000000000000000000000000100000001000000018022E100000000040005000000000000000000000000000000000100000001000000018025E10000000000000600000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001802BE10000000000000700000000000000000000000000000000010000000100000001802CE10000000004000800000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001807A8A0000000000000900000000000000000000000000000000010000000100000001807B8A0000000004000A00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180D3B00000000000000B000000000000000000000000000000000100000001000000018015B10000000004000C0000000000000000000000000000000001000000010000000180F4B00000000004000D000000000000000000000000000000000100000001000000018036B10000000004000E00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180FF88000000000400460000000000000000000000000000000001000000010000000180FE880000000004004500000000000000000000000000000000010000000100000001800B810000000004001300000000000000000000000000000000010000000100000001800C810000000004001400000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180F0880000020000000F000000000000000000000000000000000100000001000000FFFF0100120043555646696E64436F6D626F427574746F6EE80300000000000000000000000000000000000000000000000100000001000000960000000200205000000000104C4F524157414E5F4A4F494E4D4F444596000000000000000500047465737400104C4F524157414E5F4A4F494E4D4F4445114368616E67654C6F72614368616E6E656C086C6F72616D6F64650000000000000000000000000000000000000000018024E10000000000001100000000000000000000000000000000010000000100000001800A810000000000001200000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000FFFF01001500434D4643546F6F6C4261724D656E75427574746F6E2280000002000000150000002153746172742F53746F70202644656275672053657373696F6E094374726C2B46350000000000000000000000000100000001000000000000000000000001000000020021802280000000000000150000002153746172742F53746F70202644656275672053657373696F6E094374726C2B4635000000000000000000000000010000000100000000000000000000000100000000002180E0010000000000007500000021456E65726779204D6561737572656D656E742026776974686F75742044656275670000000000000000000000000100000001000000000000000000000001000000000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180C488000000000000160000000000000000000000000000000001000000010000000180C988000000000400180000000000000000000000000000000001000000010000000180C788000000000000190000000000000000000000000000000001000000010000002180C8880000000000001700000027264B696C6C20416C6C20427265616B706F696E747320696E2043757272656E7420546172676574000000000000000000000000010000000100000000000000000000000100000003002180C8880000000000001700000027264B696C6C20416C6C20427265616B706F696E747320696E2043757272656E7420546172676574000000000000000000000000010000000100000000000000000000000100000000002180E50100000000000078000000264B696C6C20416C6C20427265616B706F696E747320696E204163746976652050726F6A656374000000000000000000000000010000000100000000000000000000000100000000002180E601000000000000790000002F4B696C6C20416C6C20427265616B706F696E747320696E204D756C74692D50726F6A65637420576F726B73706163650000000000000000000000000100000001000000000000000000000001000000000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000021804C010000020001001A0000000F2650726F6A6563742057696E646F77000000000000000000000000010000000100000000000000000000000100000008002180DD880000000000001A0000000750726F6A656374000000000000000000000000010000000100000000000000000000000100000000002180DC8B0000000000003A00000005426F6F6B73000000000000000000000000010000000100000000000000000000000100000000002180E18B0000000000003B0000000946756E6374696F6E73000000000000000000000000010000000100000000000000000000000100000000002180E28B000000000000400000000954656D706C6174657300000000000000000000000001000000010000000000000000000000010000000000218018890000000000003D0000000E536F757263652042726F777365720000000000000000000000000100000001000000000000000000000001000000000021800000000000000400FFFFFFFF00000000000000000001000000000000000100000000000000000000000100000000002180D988000000000000390000000C4275696C64204F7574707574000000000000000000000000010000000100000000000000000000000100000000002180E38B000000000000410000000B46696E64204F75747075740000000000000000000000000100000001000000000000000000000001000000000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180FB7F0000000000001B000000000000000000000000000000000100000001000000000000000446696C65C6030000</Data>
          <Len>2716</Len>
          <Data>00200000010000002800FFFF01001100434D4643546F6F6C426172427574746F6E00E100000000000000000000000000000000000000000000000100000001000000018001E100000000000001000000000000000000000000000000000100000001000000018003E1000000000000020000000000000000000000000000000001000000010000000180CD7F0000000000000300000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000018023E100000000040004000000000000000000000000000000000100000001000000018022E100000000040005000000000000000000000000000000000100000001000000018025E10000000000000600000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001802BE10000000004000700000000000000000000000000000000010000000100000001802CE10000000004000800000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001807A8A0000000000000900000000000000000000000000000000010000000100000001807B8A0000000004000A00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180D3B00000000000000B000000000000000000000000000000000100000001000000018015B10000000004000C0000000000000000000000000000000001000000010000000180F4B00000000004000D000000000000000000000000000000000100000001000000018036B10000000004000E00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180FF88000000000400460000000000000000000000000000000001000000010000000180FE880000000004004500000000000000000000000000000000010000000100000001800B810000000004001300000000000000000000000000000000010000000100000001800C810000000004001400000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180F0880000020000000F000000000000000000000000000000000100000001000000FFFF0100120043555646696E64436F6D626F427574746F6EE803000000000000000000000000000000000000000000000001000000010000009600000002002050000000000D5F5F4650555F50524553454E5496000000000000000B000A5F5F4650555F555345440C41524D5F4D4154485F434D37000D5F5F4650555F50524553454E5404656C7365105F5F5441524745545F4650555F5646500B4650555F50524553454E54037468651378506F727453746172745363686564756C65720C707276456E61626C655646500B3D307845303030454438380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018024E10000000000001100000000000000000000000000000000010000000100000001800A810000000000001200000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000FFFF01001500434D4643546F6F6C4261724D656E75427574746F6E2280000002000000150000002153746172742F53746F70202644656275672053657373696F6E094374726C2B46350000000000000000000000000100000001000000000000000000000001000000020021802280000000000000150000002153746172742F53746F70202644656275672053657373696F6E094374726C2B4635000000000000000000000000010000000100000000000000000000000100000000002180E0010000000000007500000021456E65726779204D6561737572656D656E742026776974686F75742044656275670000000000000000000000000100000001000000000000000000000001000000000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180C488000000000400160000000000000000000000000000000001000000010000000180C988000000000400180000000000000000000000000000000001000000010000000180C788000000000000190000000000000000000000000000000001000000010000002180C8880000000000001700000027264B696C6C20416C6C20427265616B706F696E747320696E2043757272656E7420546172676574000000000000000000000000010000000100000000000000000000000100000003002180C8880000000000001700000027264B696C6C20416C6C20427265616B706F696E747320696E2043757272656E7420546172676574000000000000000000000000010000000100000000000000000000000100000000002180E50100000000000078000000264B696C6C20416C6C20427265616B706F696E747320696E204163746976652050726F6A656374000000000000000000000000010000000100000000000000000000000100000000002180E601000000000000790000002F4B696C6C20416C6C20427265616B706F696E747320696E204D756C74692D50726F6A65637420576F726B73706163650000000000000000000000000100000001000000000000000000000001000000000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000021804C010000020001001A0000000F2650726F6A6563742057696E646F77000000000000000000000000010000000100000000000000000000000100000008002180DD880000000000001A0000000750726F6A656374000000000000000000000000010000000100000000000000000000000100000000002180DC8B0000000000003A00000005426F6F6B73000000000000000000000000010000000100000000000000000000000100000000002180E18B0000000000003B0000000946756E6374696F6E73000000000000000000000000010000000100000000000000000000000100000000002180E28B000000000000400000000954656D706C6174657300000000000000000000000001000000010000000000000000000000010000000000218018890000000000003D0000000E536F757263652042726F777365720000000000000000000000000100000001000000000000000000000001000000000021800000000000000400FFFFFFFF00000000000000000001000000000000000100000000000000000000000100000000002180D988000000000000390000000C4275696C64204F7574707574000000000000000000000000010000000100000000000000000000000100000000002180E38B000000000000410000000B46696E64204F75747075740000000000000000000000000100000001000000000000000000000001000000000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180FB7F0000000000001B000000000000000000000000000000000100000001000000000000000446696C65C6030000</Data>
        </Buttons>
        <OriginalItems>
          <Len>1423</Len>
@@ -1822,7 +1822,7 @@
        <Name>Build</Name>
        <Buttons>
          <Len>978</Len>
          <Data>00200000010000001000FFFF01001100434D4643546F6F6C426172427574746F6ECF7F0000000000001C0000000000000000000000000000000001000000010000000180D07F0000000002001D000000000000000000000000000000000100000001000000018030800000000000001E000000000000000000000000000000000100000001000000FFFF01001500434D4643546F6F6C4261724D656E75427574746F6EC7040000000000006A0000000C4261746368204275696C2664000000000000000000000000010000000100000000000000000000000100000004000580C7040000000000006A0000000C4261746368204275696C266400000000000000000000000001000000010000000000000000000000010000000000058046070000000000006B0000000D42617463682052656275696C640000000000000000000000000100000001000000000000000000000001000000000005804707000000000000FFFFFFFF0B426174636820436C65616E0100000000000000000000000100000001000000000000000000000001000000000005809E8A0000000000001F0000000F4261746326682053657475702E2E2E000000000000000000000000010000000100000000000000000000000100000000000180D17F0000000004002000000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001804C8A0000000000002100000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000FFFF01001900434D4643546F6F6C426172436F6D626F426F78427574746F6EBA00000000000000000000000000000000000000000000000001000000010000009600000003002050000000000953544D333248373433960000000000000001000953544D333248373433000000000180EB880000000000002200000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180C07F000000000000230000000000000000000000000000000001000000010000000180B08A000000000400240000000000000000000000000000000001000000010000000180A8010000000000004E00000000000000000000000000000000010000000100000001807202000000000000530000000000000000000000000000000001000000010000000180BE010000000000005000000000000000000000000000000000010000000100000000000000054275696C64DC010000</Data>
          <Data>00200000010000001000FFFF01001100434D4643546F6F6C426172427574746F6ECF7F0000000004001C0000000000000000000000000000000001000000010000000180D07F0000000002001D000000000000000000000000000000000100000001000000018030800000000000001E000000000000000000000000000000000100000001000000FFFF01001500434D4643546F6F6C4261724D656E75427574746F6EC7040000000000006A0000000C4261746368204275696C2664000000000000000000000000010000000100000000000000000000000100000004000580C7040000000000006A0000000C4261746368204275696C266400000000000000000000000001000000010000000000000000000000010000000000058046070000000000006B0000000D42617463682052656275696C640000000000000000000000000100000001000000000000000000000001000000000005804707000000000000FFFFFFFF0B426174636820436C65616E0100000000000000000000000100000001000000000000000000000001000000000005809E8A0000000000001F0000000F4261746326682053657475702E2E2E000000000000000000000000010000000100000000000000000000000100000000000180D17F0000000004002000000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001804C8A0000000000002100000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000FFFF01001900434D4643546F6F6C426172436F6D626F426F78427574746F6EBA00000000000000000000000000000000000000000000000001000000010000009600000003002050000000000953544D333248373433960000000000000001000953544D333248373433000000000180EB880000000000002200000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180C07F000000000000230000000000000000000000000000000001000000010000000180B08A000000000400240000000000000000000000000000000001000000010000000180A8010000000000004E00000000000000000000000000000000010000000100000001807202000000000000530000000000000000000000000000000001000000010000000180BE010000000000005000000000000000000000000000000000010000000100000000000000054275696C64DC010000</Data>
        </Buttons>
        <OriginalItems>
          <Len>583</Len>
@@ -1838,7 +1838,7 @@
        <Name>Debug</Name>
        <Buttons>
          <Len>2373</Len>
          <Data>00200000000000001900FFFF01001100434D4643546F6F6C426172427574746F6ECC880000000000002500000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000018017800000000000002600000000000000000000000000000000010000000100000001801D800000000000002700000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001801A800000000000002800000000000000000000000000000000010000000100000001801B80000000000000290000000000000000000000000000000001000000010000000180E57F0000000000002A00000000000000000000000000000000010000000100000001801C800000000000002B00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000018000890000000000002C00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180E48B0000000000002D0000000000000000000000000000000001000000010000000180F07F0000000000002E0000000000000000000000000000000001000000010000000180E8880000000000003700000000000000000000000000000000010000000100000001803B010000000000002F0000000000000000000000000000000001000000010000000180BB8A00000000000030000000000000000000000000000000000100000001000000FFFF01001500434D4643546F6F6C4261724D656E75427574746F6E0E01000000000000310000000D57617463682057696E646F7773000000000000000000000000010000000100000000000000000000000100000003001380D88B00000000000031000000085761746368202631000000000000000000000000010000000100000000000000000000000100000000001380D98B00000000000031000000085761746368202632000000000000000000000000010000000100000000000000000000000100000000001380CE01000000000000FFFFFFFF0C576174636820416E63686F720100000000000000010000000000000001000000000000000000000001000000000013800F01000000000000320000000E4D656D6F72792057696E646F7773000000000000000000000000010000000100000000000000000000000100000004001380D28B00000000000032000000094D656D6F7279202631000000000000000000000000010000000100000000000000000000000100000000001380D38B00000000000032000000094D656D6F7279202632000000000000000000000000010000000100000000000000000000000100000000001380D48B00000000000032000000094D656D6F7279202633000000000000000000000000010000000100000000000000000000000100000000001380D58B00000000000032000000094D656D6F72792026340000000000000000000000000100000001000000000000000000000001000000000013801001000000000000330000000E53657269616C2057696E646F77730000000000000000000000000100000001000000000000000000000001000000040013809307000000000000330000000855415254202326310000000000000000000000000100000001000000000000000000000001000000000013809407000000000000330000000855415254202326320000000000000000000000000100000001000000000000000000000001000000000013809507000000000000330000000855415254202326330000000000000000000000000100000001000000000000000000000001000000000013809607000000000000330000001626446562756720287072696E746629205669657765720000000000000000000000000100000001000000000000000000000001000000000013803C010000000000007200000010416E616C797369732057696E646F7773000000000000000000000000010000000100000000000000000000000100000004001380658A000000000000340000000F264C6F67696320416E616C797A6572000000000000000000000000010000000100000000000000000000000100000000001380DC7F0000000000003E0000001526506572666F726D616E636520416E616C797A6572000000000000000000000000010000000100000000000000000000000100000000001380E788000000000000380000000E26436F646520436F766572616765000000000000000000000000010000000100000000000000000000000100000000001380CD01000000000000FFFFFFFF0F416E616C7973697320416E63686F7201000000000000000100000000000000010000000000000000000000010000000000138053010000000000003F0000000D54726163652057696E646F77730000000000000000000000000100000001000000000000000000000001000000010013805401000000000000FFFFFFFF115472616365204D656E7520416E63686F720100000000000000010000000000000001000000000000000000000001000000000013802901000000000000350000001553797374656D205669657765722057696E646F77730000000000000000000000000100000001000000000000000000000001000000010013804B01000000000000FFFFFFFF1453797374656D2056696577657220416E63686F720100000000000000010000000000000001000000000000000000000001000000000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000013800189000000000000360000000F26546F6F6C626F782057696E646F7700000000000000000000000001000000010000000000000000000000010000000300138044C5000000000000FFFFFFFF0E5570646174652057696E646F77730100000000000000010000000000000001000000000000000000000001000000000013800000000000000400FFFFFFFF000000000000000000010000000000000001000000000000000000000001000000000013805B01000000000000FFFFFFFF12546F6F6C626F78204D656E75416E63686F72010000000000000001000000000000000100000000000000000000000100000000000000000005446562756764020000</Data>
          <Data>00200000000000001900FFFF01001100434D4643546F6F6C426172427574746F6ECC880000000000002500000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000018017800000000000002600000000000000000000000000000000010000000100000001801D800000000000002700000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000001801A800000000000002800000000000000000000000000000000010000000100000001801B80000000000000290000000000000000000000000000000001000000010000000180E57F0000000000002A00000000000000000000000000000000010000000100000001801C800000000000002B00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF000000000000000000000000000100000001000000018000890000000000002C00000000000000000000000000000000010000000100000001800000000001000000FFFFFFFF0000000000000000000000000001000000010000000180E48B0000000000002D0000000000000000000000000000000001000000010000000180F07F0000000000002E0000000000000000000000000000000001000000010000000180E8880000000000003700000000000000000000000000000000010000000100000001803B010000000000002F0000000000000000000000000000000001000000010000000180BB8A00000000000030000000000000000000000000000000000100000001000000FFFF01001500434D4643546F6F6C4261724D656E75427574746F6E0E01000000000000310000000D57617463682057696E646F7773000000000000000000000000010000000100000000000000000000000100000003001380D88B00000000000031000000085761746368202631000000000000000000000000010000000100000000000000000000000100000000001380D98B00000000000031000000085761746368202632000000000000000000000000010000000100000000000000000000000100000000001380CE01000000000000FFFFFFFF0C576174636820416E63686F720000000000000000010000000000000001000000000000000000000001000000000013800F01000000000000320000000E4D656D6F72792057696E646F7773000000000000000000000000010000000100000000000000000000000100000004001380D28B00000000000032000000094D656D6F7279202631000000000000000000000000010000000100000000000000000000000100000000001380D38B00000000000032000000094D656D6F7279202632000000000000000000000000010000000100000000000000000000000100000000001380D48B00000000000032000000094D656D6F7279202633000000000000000000000000010000000100000000000000000000000100000000001380D58B00000000000032000000094D656D6F72792026340000000000000000000000000100000001000000000000000000000001000000000013801001000000000000330000000E53657269616C2057696E646F77730000000000000000000000000100000001000000000000000000000001000000040013809307000000000000330000000855415254202326310000000000000000000000000100000001000000000000000000000001000000000013809407000000000000330000000855415254202326320000000000000000000000000100000001000000000000000000000001000000000013809507000000000000330000000855415254202326330000000000000000000000000100000001000000000000000000000001000000000013809607000000000000330000001626446562756720287072696E746629205669657765720000000000000000000000000100000001000000000000000000000001000000000013803C010000000000007200000010416E616C797369732057696E646F7773000000000000000000000000010000000100000000000000000000000100000004001380658A000000000000340000000F264C6F67696320416E616C797A6572000000000000000000000000010000000100000000000000000000000100000000001380DC7F0000000000003E0000001526506572666F726D616E636520416E616C797A6572000000000000000000000000010000000100000000000000000000000100000000001380E788000000000000380000000E26436F646520436F766572616765000000000000000000000000010000000100000000000000000000000100000000001380CD01000000000000FFFFFFFF0F416E616C7973697320416E63686F7200000000000000000100000000000000010000000000000000000000010000000000138053010000000000003F0000000D54726163652057696E646F77730000000000000000000000000100000001000000000000000000000001000000010013805401000000000000FFFFFFFF115472616365204D656E7520416E63686F720000000000000000010000000000000001000000000000000000000001000000000013802901000000000000350000001553797374656D205669657765722057696E646F77730000000000000000000000000100000001000000000000000000000001000000010013804B01000000000000FFFFFFFF1453797374656D2056696577657220416E63686F720000000000000000010000000000000001000000000000000000000001000000000001800000000001000000FFFFFFFF00000000000000000000000000010000000100000013800189000000000000360000000F26546F6F6C626F782057696E646F7700000000000000000000000001000000010000000000000000000000010000000300138044C5000000000000FFFFFFFF0E5570646174652057696E646F77730000000000000000010000000000000001000000000000000000000001000000000013800000000000000400FFFFFFFF000000000000000000010000000000000001000000000000000000000001000000000013805B01000000000000FFFFFFFF12546F6F6C626F78204D656E75416E63686F72000000000000000001000000000000000100000000000000000000000100000000000000000005446562756764020000</Data>
        </Buttons>
        <OriginalItems>
          <Len>898</Len>
@@ -3603,381 +3603,21 @@
    <ActiveMDIGroup>0</ActiveMDIGroup>
    <MDIGroup>
      <Size>100</Size>
      <ActiveTab>8</ActiveTab>
      <ActiveTab>1</ActiveTab>
      <Doc>
        <Name>..\FML\Internet\Module\EG800\EG800FSM.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>237</TopLine>
        <CurrentLine>251</CurrentLine>
        <Name>../Core/Src/system_stm32h7xx.c</Name>
        <ColumnNumber>41</ColumnNumber>
        <TopLine>176</TopLine>
        <CurrentLine>187</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>startup_stm32h743xx.s</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>230</TopLine>
        <CurrentLine>243</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Middlewares/Third_Party/FreeRTOS/Source/tasks.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>3638</TopLine>
        <CurrentLine>3650</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\app.c</Name>
        <ColumnNumber>9</ColumnNumber>
        <TopLine>46</TopLine>
        <CurrentLine>54</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Middlewares/Third_Party/FreeRTOS/Source/include/semphr.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>278</TopLine>
        <CurrentLine>289</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\Internet\Socket.c</Name>
        <ColumnNumber>18</ColumnNumber>
        <TopLine>686</TopLine>
        <CurrentLine>700</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\Internet\Module\EG800\EG800Driver.c</Name>
        <ColumnNumber>44</ColumnNumber>
        <TopLine>645</TopLine>
        <CurrentLine>654</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\DBG.c</Name>
        <ColumnNumber>35</ColumnNumber>
        <TopLine>421</TopLine>
        <CurrentLine>435</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\UDPClient.c</Name>
        <ColumnNumber>4</ColumnNumber>
        <TopLine>474</TopLine>
        <CurrentLine>485</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\HAL\Uart.c</Name>
        <ColumnNumber>5</ColumnNumber>
        <TopLine>78</TopLine>
        <CurrentLine>92</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Core/Src/main.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>125</TopLine>
        <CurrentLine>126</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\bluetooth.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>153</TopLine>
        <CurrentLine>167</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\global_param.c</Name>
        <ColumnNumber>22</ColumnNumber>
        <TopLine>36</TopLine>
        <CurrentLine>49</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\AppConfig.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>26</TopLine>
        <CurrentLine>40</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\HAL\MCUFlash.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>267</TopLine>
        <CurrentLine>278</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Core/Src/stm32h7xx_it.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>104</TopLine>
        <CurrentLine>105</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\global_param.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>57</TopLine>
        <CurrentLine>66</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_flash.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>288</TopLine>
        <CurrentLine>302</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Drivers/STM32H7xx_HAL_Driver/Inc/stm32h7xx_hal_flash.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>181</TopLine>
        <CurrentLine>192</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\NTRIPApp.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>1</TopLine>
        <CurrentLine>1</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\Shell.c</Name>
        <ColumnNumber>19</ColumnNumber>
        <TopLine>223</TopLine>
        <CurrentLine>224</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\TCPClient.c</Name>
        <ColumnNumber>13</ColumnNumber>
        <TopLine>60</TopLine>
        <CurrentLine>71</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../HIDOLibrary/Include/HIDO_Shell.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>3</TopLine>
        <CurrentLine>17</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>D:\Keil_v5\ARM\ARMCC\include\stdio.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>1</TopLine>
        <CurrentLine>1</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\GPS.c</Name>
        <ColumnNumber>83</ColumnNumber>
        <TopLine>220</TopLine>
        <CurrentLine>234</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../HAL/Uart.h</Name>
        <ColumnNumber>15</ColumnNumber>
        <TopLine>36</TopLine>
        <CurrentLine>42</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_uart.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>306</TopLine>
        <CurrentLine>307</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Core/Src/stm32h7xx_hal_msp.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>371</TopLine>
        <CurrentLine>385</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\APL\serial_at_cmd_app.c</Name>
        <ColumnNumber>21</ColumnNumber>
        <TopLine>4</TopLine>
        <CurrentLine>14</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\SBUS.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>262</TopLine>
        <CurrentLine>276</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../HIDOLibrary/Include/HIDO_TYpeDef.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>29</TopLine>
        <CurrentLine>39</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>251</TopLine>
        <CurrentLine>265</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\pwm_ctrol.c</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>136</TopLine>
        <CurrentLine>150</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../HIDOLibrary/Include/HIDO_Util.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>1</TopLine>
        <CurrentLine>1</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../FML/pwm_ctrol.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>1</TopLine>
        <CurrentLine>1</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\PythonLink.c</Name>
        <ColumnNumber>12</ColumnNumber>
        <TopLine>385</TopLine>
        <CurrentLine>399</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>E:\GIT\Lawnmower_STM32H7\STM32H743\HIDOLibrary\Include\HIDO_ModbusCRC16.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>1</TopLine>
        <CurrentLine>1</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>..\FML\PythonLink.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>20</TopLine>
        <CurrentLine>32</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Middlewares/Third_Party/FreeRTOS/Source/include/FreeRTOS.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>929</TopLine>
        <CurrentLine>939</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../FML/GPS.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>1</TopLine>
        <CurrentLine>1</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2/cmsis_os2.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>87</TopLine>
        <CurrentLine>101</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
      </Doc>
      <Doc>
        <Name>../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F/portmacro.h</Name>
        <ColumnNumber>0</ColumnNumber>
        <TopLine>65</TopLine>
        <CurrentLine>74</CurrentLine>
        <Name>../Drivers/CMSIS/Include/core_cm7.h</Name>
        <ColumnNumber>8</ColumnNumber>
        <TopLine>122</TopLine>
        <CurrentLine>134</CurrentLine>
        <Folding>1</Folding>
        <ContractedFolders></ContractedFolders>
        <PaneID>0</PaneID>
STM32H743/MDK-ARM/STM32H743.uvprojx
@@ -337,7 +337,7 @@
            <v6Rtti>0</v6Rtti>
            <VariousControls>
              <MiscControls></MiscControls>
              <Define>USE_PWR_LDO_SUPPLY,USE_HAL_DRIVER,STM32H743xx,_USE_OS_,_RTK_MODE_,__EC600S__,__APP_CODE__,__USE_UWB__,__USE_NTRIP__</Define>
              <Define>USE_PWR_LDO_SUPPLY,USE_HAL_DRIVER,STM32H743xx,_USE_OS_,ARM_MATH_DSP</Define>
              <Undefine></Undefine>
              <IncludePath>../Core/Inc;../Drivers/STM32H7xx_HAL_Driver/Inc;../Drivers/STM32H7xx_HAL_Driver/Inc/Legacy;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Drivers/CMSIS/Device/ST/STM32H7xx/Include;../Drivers/CMSIS/Include;../HIDOLibrary/Include;../HAL;../FML;../APL;../FML/Internet;../FML/Internet/Module;../FML/Internet/Module/EG800</IncludePath>
            </VariousControls>
@@ -740,8 +740,14 @@
  <RTE>
    <apis/>
    <components>
      <component Cclass="CMSIS" Cgroup="CORE" Cvendor="ARM" Cversion="5.2.0" condition="ARMv6_7_8-M Device">
        <package name="CMSIS" schemaVersion="1.3" url="http://www.keil.com/pack/" vendor="ARM" version="5.5.1"/>
      <component Cclass="CMSIS" Cgroup="CORE" Cvendor="ARM" Cversion="5.5.0" condition="ARMv6_7_8-M Device">
        <package name="CMSIS" schemaVersion="1.3" url="http://www.keil.com/pack/" vendor="ARM" version="5.8.0"/>
        <targetInfos>
          <targetInfo name="STM32H743"/>
        </targetInfos>
      </component>
      <component Cclass="CMSIS" Cgroup="DSP" Cvariant="Source" Cvendor="ARM" Cversion="1.9.0-dev" condition="CMSIS DSP">
        <package name="CMSIS" schemaVersion="1.3" url="http://www.keil.com/pack/" vendor="ARM" version="5.8.0"/>
        <targetInfos>
          <targetInfo name="STM32H743"/>
        </targetInfos>
STM32H743/MDK-ARM/STM32H743/STM32H743.lnp
@@ -60,6 +60,34 @@
"stm32h743\heap_4.o"
"stm32h743\port.o"
"..\HIDOLibrary\HIDOLibrary.lib"
"stm32h743\basicmathfunctions.o"
"stm32h743\basicmathfunctionsf16.o"
"stm32h743\bayesfunctions.o"
"stm32h743\bayesfunctionsf16.o"
"stm32h743\commontables.o"
"stm32h743\commontablesf16.o"
"stm32h743\complexmathfunctions.o"
"stm32h743\complexmathfunctionsf16.o"
"stm32h743\controllerfunctions.o"
"stm32h743\distancefunctions.o"
"stm32h743\distancefunctionsf16.o"
"stm32h743\fastmathfunctions.o"
"stm32h743\fastmathfunctionsf16.o"
"stm32h743\filteringfunctions.o"
"stm32h743\filteringfunctionsf16.o"
"stm32h743\interpolationfunctions.o"
"stm32h743\interpolationfunctionsf16.o"
"stm32h743\matrixfunctions.o"
"stm32h743\matrixfunctionsf16.o"
"stm32h743\quaternionmathfunctions.o"
"stm32h743\svmfunctions.o"
"stm32h743\svmfunctionsf16.o"
"stm32h743\statisticsfunctions.o"
"stm32h743\statisticsfunctionsf16.o"
"stm32h743\supportfunctions.o"
"stm32h743\supportfunctionsf16.o"
"stm32h743\transformfunctions.o"
"stm32h743\transformfunctionsf16.o"
--strict --scatter "STM32H743\STM32H743.sct"
--summary_stderr --info summarysizes --map --load_addr_map_info --xref --callgraph --symbols
--info sizes --info totals --info unused --info veneers
STM32H743/MDK-ARM/STM32H743/STM32H743.map
ÎļþÌ«´ó
STM32H743/MDK-ARM/STM32H743/STM32H743_STM32H743.dep
ÎļþÌ«´ó
STM32H743/MDK-ARM/startup_stm32h743xx.lst
@@ -1285,9 +1285,11 @@
../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2 -I../Middlewares/Third
_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F -I../Drivers/CMSIS/Device/ST/STM3
2H7xx/Include -I../Drivers/CMSIS/Include -I.\RTE\_STM32H743 -ID:\Users\zhyin\Ap
pData\Local\Arm\Packs\ARM\CMSIS\5.8.0\CMSIS\Core\Include --predefine="__UVISION
_VERSION SETA 530" --predefine="_RTE_ SETA 1" --predefine="STM32H743xx SETA 1"
--predefine="_RTE_ SETA 1" --list=startup_stm32h743xx.lst startup_stm32h743xx.s
pData\Local\Arm\Packs\ARM\CMSIS\5.8.0\CMSIS\Core\Include -ID:\Users\zhyin\AppDa
ta\Local\Arm\Packs\ARM\CMSIS\5.8.0\CMSIS\DSP\Include -ID:\Users\zhyin\AppData\L
ocal\Arm\Packs\ARM\CMSIS\5.8.0\CMSIS\DSP\PrivateInclude --predefine="__UVISION_
VERSION SETA 530" --predefine="_RTE_ SETA 1" --predefine="STM32H743xx SETA 1" -
-predefine="_RTE_ SETA 1" --list=startup_stm32h743xx.lst startup_stm32h743xx.s
STM32H743/Middlewares/ST/ARM/DSP/Inc/arm_math.h
¶Ô±ÈÐÂÎļþ
ÎļþÌ«´ó
STM32H743/Middlewares/Third_Party/ARM/DSP/LICENSE.txt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,201 @@
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/
   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
   1. Definitions.
      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.
      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.
      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.
      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.
      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.
      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.
      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).
      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.
      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."
      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.
   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.
   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.
   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:
      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and
      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and
      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and
      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.
      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.
   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.
   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.
   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.
   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.
   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.
   END OF TERMS AND CONDITIONS
   APPENDIX: How to apply the Apache License to your work.
      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.
   Copyright {yyyy} {name of copyright owner}
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
       http://www.apache.org/licenses/LICENSE-2.0
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
STM32H743/STM32H743.ioc
@@ -281,7 +281,8 @@
Mcu.Pin8=PA2
Mcu.Pin9=PA3
Mcu.PinsNb=36
Mcu.ThirdPartyNb=0
Mcu.ThirdParty0=STMicroelectronics.X-CUBE-ALGOBUILD.1.4.0
Mcu.ThirdPartyNb=1
Mcu.UserConstants=
Mcu.UserName=STM32H743VITx
MxCube.Version=6.15.0
@@ -517,6 +518,7 @@
SH.S_TIM3_CH2.ConfNb=1
SH.S_TIM4_CH1.0=TIM4_CH1,Input_Capture1_from_TI1
SH.S_TIM4_CH1.ConfNb=1
STMicroelectronics.X-CUBE-ALGOBUILD.1.4.0_SwParameter=LibraryCcDSPOoLibraryJjDSPOoLibrary\:true;
TIM1.AutoReloadPreload=TIM_AUTORELOAD_PRELOAD_DISABLE
TIM1.Channel-PWM\ Generation1\ CH1=TIM_CHANNEL_1
TIM1.Channel-PWM\ Generation2\ CH2=TIM_CHANNEL_2
python/__pycache__/gps_imu_receiver.cpython-310.pyc
Binary files differ
python/__pycache__/gps_imu_receiver.cpython-313.pyc
Binary files differ
python/gecaolujing2.txt
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1 @@
2.65,-5.83;30.10,-52.70;30.10,-52.70;32.70,-53.19;32.70,-53.19;2.02,-0.80;2.02,-0.80;4.15,-0.48;4.15,-0.48;34.49,-52.28;34.49,-52.28;36.26,-51.34;36.26,-51.34;6.28,-0.15;6.28,-0.15;8.40,0.17;8.40,0.17;38.02,-50.40;38.02,-50.40;39.79,-49.45;39.79,-49.45;10.53,0.49;10.53,0.49;12.66,0.82;12.66,0.82;41.56,-48.51;41.56,-48.51;43.32,-47.57;43.32,-47.57;14.79,1.14;14.79,1.14;16.92,1.46;16.92,1.46;45.09,-46.63;45.09,-46.63;46.82,-45.64;46.82,-45.64;19.05,1.79;19.05,1.79;21.18,2.11;21.18,2.11;48.53,-44.60;48.53,-44.60;50.24,-43.56;50.24,-43.56;23.30,2.43;23.30,2.43;25.43,2.76;25.43,2.76;51.95,-42.52;51.95,-42.52;53.66,-41.48;53.66,-41.48;27.56,3.08;27.56,3.08;29.77,3.27;29.77,3.27;55.37,-40.44
python/gui_state.json
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
{
  "path_file": "E:/GIT/Lawnmower_STM32H7/python/gecaolujing2.txt",
  "origin_gga": "$GNGGA,064327.100,3949.8885025,N,11616.7556143,E,4,31,0.52,46.617,M,-6.678,M,1.0,0409*7E",
  "serial_port": "COM17"
}
python/hitl/README.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,50 @@
# ç¡¬ä»¶åœ¨çޝ (HITL) ä»¿çœŸæ¦‚è¿°
为了在 STM32H7 ä¸ŠéªŒè¯è¿åŠ¨æŽ§åˆ¶ç®—æ³•ï¼ŒåŒæ—¶é¿å…çœŸå®žå‰²è‰çŽ¯å¢ƒå¸¦æ¥çš„é£Žé™©ï¼Œ`python/hitl` å­ç›®å½•负责实现电脑仿真 â†” STM32H7 çš„闭环调试工具。核心目标:
- **传感器仿真**:Python ç«¯ä»¥ GPS æ¨¡å—真实协议输出 `$GPFMI`(10 Hz)和 `$GPIMU`(100 Hz)数据,通过串口 2 è¾“入到 STM32H7。
- **控制闭环**:STM32H7 åœ¨æŽ¥æ”¶åˆ°ä»¿çœŸä¼ æ„Ÿå™¨æ•°æ®åŽæ‰§è¡ŒåŽŸæœ‰æŽ§åˆ¶ç®—æ³•ï¼Œå¹¶ä¾ç…§ `PythonLink` åè®®ï¼ˆå‰è¿›ã€è½¬å‘两个量)通过串口 2 å›žä¼ ç»™ Python,驱动仿真器更新车辆状态。
- **日志采集**:STM32H7 é€šè¿‡ä¸²å£ 5(921600 bps)输出调试日志,Python ç«¯å®žæ—¶è§£æžä¸Žä¿å­˜ï¼Œä¸ºå®žè½¦è°ƒè¯•提供参考。
## ä¸²å£ä¸Žåè®®æ˜ å°„
| åŠŸèƒ½ | STM32H7 ä¸²å£ | æ–¹å‘ | åè®®/内容 | é¢‘率 |
| --- | --- | --- | --- | --- |
| ä¼ æ„Ÿå™¨ä»¿çœŸè¾“å…¥ | UART2 (RX) | Python â†’ STM32 | `$GPFMI`, `$GPIMU` | 10 Hz / 100 Hz |
| æŽ§åˆ¶è¾“出回传 | UART2 (TX) | STM32 â†’ Python | `PythonLink` æŽ§åˆ¶å¸§ï¼ˆå‰è¿›/转向) | â‰ˆ æŽ§åˆ¶é¢‘率 |
| è°ƒè¯•日志 | UART5 (TX) | STM32 â†’ Python | çº¯æ–‡æœ¬æˆ–协议日志 | å®žæ—¶ï¼Œ921600 bps |
## æ•°æ®æµæµç¨‹å›¾
```mermaid
flowchart LR
    subgraph PC仿真端
        Sim[仿真器\n状态积分]
        GPFMI[GPFMI帧生成\n10 Hz]
        GPIMU[GPIMU帧生成\n100 Hz]
        CtrlRx[PythonLink控制帧接收]
        LogSink[串口5日志解析/保存]
    end
    subgraph STM32H7
        UART2["UART2\n(GPS/IMU输入 & æŽ§åˆ¶è¾“出)"]
        CtrlAlgo[运动控制算法\n(PID + çº¯è·Ÿè¸ª)]
        UART5["UART5\n(921600) æ—¥å¿—输出"]
    end
    Sim --> GPFMI --> |UART2 TX| UART2
    Sim --> GPIMU --> |UART2 TX| UART2
    UART2 --> CtrlAlgo
    CtrlAlgo --> |前进/转向控制帧| UART2 --> CtrlRx --> Sim
    CtrlAlgo --> |运行日志| UART5 --> |串口5 RX| LogSink
```
## åŽç»­å®žçŽ°è¦ç‚¹
1. **协议复用**:严格复用现有 GPS `$GPFMI`/`$GPIMU` è¾“出格式与 `PythonLink` æŽ§åˆ¶å¸§ï¼Œä¿è¯å®žè½¦ä¸Žä»¿çœŸé—´å¯æ— ç¼åˆ‡æ¢ã€‚
2. **时间同步**:仿真器内部需维护 UTC æ—¶é—´æˆ³ï¼ˆWeek/ToW ä¸Ž `hhmmss.ss`),让 STM32H7 é€»è¾‘保持一致。
3. **高频任务划分**:建议使用独立线程/异步任务分别发送 10 Hz ä¸Ž 100 Hz æ•°æ®å¸§ï¼Œå¹¶å¯¹æŽ§åˆ¶å›žä¼ ã€æ—¥å¿—接收进行非阻塞处理。
4. **仿真模型**:后续可扩展为读取规划路径,利用 ENU åæ ‡ç³»è¿›è¡Œè¿åŠ¨å­¦ç§¯åˆ†ï¼Œå¹¶æŠŠç»“æžœæ˜ å°„å›žç»çº¬åº¦/惯导量。
5. **数据持久化**:日志与仿真配置(路径、初始原点、串口号)应保存以便复现测试场景。
该 README å°†ä½œä¸º HITL å­ç³»ç»Ÿçš„设计索引,具体实现位于同目录下的 Python æ¨¡å—中。
python/hitl/__init__.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,9 @@
"""
硬件在环 (HITL) ä»¿çœŸå­åŒ…。
该包将包含:
- GPS/IMU æ•°æ®ä»¿çœŸä¸Žåè®®å°è£…
- PythonLink æŽ§åˆ¶å¸§æ¡¥æŽ¥
- ä¸²å£æ—¥å¿—解析工具
"""
python/hitl/__pycache__/__init__.cpython-310.pyc
Binary files differ
python/hitl/__pycache__/dynamics.cpython-310.pyc
Binary files differ
python/hitl/__pycache__/geo.cpython-310.pyc
Binary files differ
python/hitl/__pycache__/protocols.cpython-310.pyc
Binary files differ
python/hitl/__pycache__/simulator.cpython-310.pyc
Binary files differ
python/hitl/dynamics.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,130 @@
"""
差速履带运动学/动力学模型。
"""
from __future__ import annotations
import dataclasses
import math
import random
from typing import Tuple
GRAVITY = 9.80665  # m/s^2
def _clamp(value: float, min_value: float, max_value: float) -> float:
    return max(min_value, min(max_value, value))
def _wrap_angle(rad: float) -> float:
    """Wrap angle to [-pi, pi)."""
    pi2 = math.tau  # 2*pi
    while rad >= math.pi:
        rad -= pi2
    while rad < -math.pi:
        rad += pi2
    return rad
@dataclasses.dataclass
class DifferentialDriveState:
    east: float = 0.0
    north: float = 0.0
    up: float = 0.0
    heading: float = 0.0  # rad, æ•°å­¦åæ ‡ç³» (东=0, CCW>0)
    linear_velocity: float = 0.0  # m/s
    angular_velocity: float = 0.0  # rad/s
    pitch_deg: float = 0.0
    roll_deg: float = 0.0
    east_velocity: float = 0.0
    north_velocity: float = 0.0
    up_velocity: float = 0.0
    body_accel_g: Tuple[float, float, float] = (0.0, 0.0, -1.0)
    gyro_deg_s: Tuple[float, float, float] = (0.0, 0.0, 0.0)
    temperature_c: float = 30.0
    def copy(self) -> "DifferentialDriveState":
        return dataclasses.replace(self)
class DifferentialDriveModel:
    """准静态差速履带模型,可模拟原地转向。"""
    def __init__(
        self,
        track_width: float = 0.8,
        max_linear_speed: float = 2.0,
        max_linear_accel: float = 1.5,
        max_angular_speed: float = math.radians(120.0),
        max_angular_accel: float = math.radians(180.0),
    ):
        self.track_width = track_width
        self.max_linear_speed = max_linear_speed
        self.max_linear_accel = max_linear_accel
        self.max_angular_speed = max_angular_speed
        self.max_angular_accel = max_angular_accel
        self.state = DifferentialDriveState()
        self._rng = random.Random(42)
    def reset(self, east: float = 0.0, north: float = 0.0, up: float = 0.0, heading_deg: float = 0.0):
        self.state = DifferentialDriveState(east=east, north=north, up=up, heading=math.radians(heading_deg))
    def step(self, target_linear: float, target_angular: float, dt: float) -> DifferentialDriveState:
        if dt <= 0.0:
            return self.state
        target_linear = _clamp(target_linear, -self.max_linear_speed, self.max_linear_speed)
        target_angular = _clamp(target_angular, -self.max_angular_speed, self.max_angular_speed)
        dv = target_linear - self.state.linear_velocity
        max_dv = self.max_linear_accel * dt
        dv = _clamp(dv, -max_dv, max_dv)
        linear_acc = dv / dt
        self.state.linear_velocity += dv
        dw = target_angular - self.state.angular_velocity
        max_dw = self.max_angular_accel * dt
        dw = _clamp(dw, -max_dw, max_dw)
        angular_acc = dw / dt
        self.state.angular_velocity += dw
        # æ›´æ–°å§¿æ€ä¸Žä½ç½®
        self.state.heading = _wrap_angle(self.state.heading + self.state.angular_velocity * dt)
        cos_h = math.cos(self.state.heading)
        sin_h = math.sin(self.state.heading)
        self.state.east += self.state.linear_velocity * cos_h * dt
        self.state.north += self.state.linear_velocity * sin_h * dt
        self.state.up += 0.0  # å¯æ‰©å±•地形模型
        # é€Ÿåº¦ (ENU)
        self.state.east_velocity = self.state.linear_velocity * cos_h
        self.state.north_velocity = self.state.linear_velocity * sin_h
        self.state.up_velocity = 0.0
        # ä½“轴加速度:前向加速度 + å‘心加速度
        lateral_acc = self.state.linear_velocity * self.state.angular_velocity
        ax_g = linear_acc / GRAVITY
        ay_g = lateral_acc / GRAVITY
        az_g = -1.0  # å‡è®¾æ°´å¹³åœ°é¢
        self.state.body_accel_g = (
            ax_g + self._rng.gauss(0.0, 0.002),
            ay_g + self._rng.gauss(0.0, 0.002),
            az_g + self._rng.gauss(0.0, 0.0005),
        )
        # å§¿æ€è¿‘似:由加速度分量映射成小角度
        self.state.pitch_deg = math.degrees(math.atan2(ax_g, 1.0))
        self.state.roll_deg = math.degrees(math.atan2(-ay_g, 1.0))
        self.state.gyro_deg_s = (
            self._rng.gauss(0.0, 0.01),
            self._rng.gauss(0.0, 0.01),
            math.degrees(self.state.angular_velocity) + self._rng.gauss(0.0, 0.1),
        )
        temp_drift = self._rng.gauss(0.0, 0.0005)
        self.state.temperature_c += temp_drift
        return self.state
python/hitl/geo.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,171 @@
"""
地理坐标与参考系转换工具。
提供以下能力:
- è§£æž GGA/自定义格式的原点配置
- WGS-84 ä¸‹çš„ LLA â†” ECEF â†” ENU äº’转
- å°† ENU åæ ‡åç®—为经纬度
"""
from __future__ import annotations
import dataclasses
import math
from typing import Optional, Tuple
WGS84_A = 6378137.0  # åŠé•¿è½´
WGS84_F = 1 / 298.257223563
WGS84_E2 = WGS84_F * (2 - WGS84_F)
WGS84_B = WGS84_A * (1 - WGS84_F)
WGS84_EP2 = (WGS84_A ** 2 - WGS84_B ** 2) / (WGS84_B ** 2)
def dm_to_decimal(dm_value: float) -> float:
    """度分转十进制度。"""
    degrees = int(dm_value // 100)
    minutes = dm_value - degrees * 100
    return degrees + minutes / 60.0
@dataclasses.dataclass(frozen=True)
class Origin:
    """ENU å‚考原点。"""
    latitude_deg: float
    longitude_deg: float
    altitude_m: float
    def __post_init__(self):
        object.__setattr__(self, "latitude_rad", math.radians(self.latitude_deg))
        object.__setattr__(self, "longitude_rad", math.radians(self.longitude_deg))
        object.__setattr__(self, "ecef", geo_to_ecef(self.latitude_deg, self.longitude_deg, self.altitude_m))
def parse_origin(origin_geo: str) -> Origin:
    """
    è§£æžåŽŸç‚¹æè¿°:
        1) çº¯ GGA æŠ¥æ–‡: "$GNGGA,..."
        2) ç®€å†™: "3949.8890,N,11616.7555,E[,alt]"
    """
    text = (origin_geo or "").strip()
    if not text:
        raise ValueError("原点字符串不能为空")
    if text.startswith("$") and "GGA" in text.upper():
        parts = text.split(",")
        if len(parts) < 10:
            raise ValueError("GGA æŠ¥æ–‡å­—段数量不足")
        lat_dm = float(parts[2])
        lat_dir = parts[3].upper()
        lon_dm = float(parts[4])
        lon_dir = parts[5].upper()
        alt = float(parts[9])
        lat = dm_to_decimal(lat_dm)
        lon = dm_to_decimal(lon_dm)
        if lat_dir == "S":
            lat = -lat
        if lon_dir == "W":
            lon = -lon
        return Origin(lat, lon, alt)
    # å…¼å®¹ ",lat,N,lon,E" æˆ– "lat,N,lon,E"
    text = text.lstrip(",")
    parts = [p.strip() for p in text.split(",") if p.strip()]
    if len(parts) not in (4, 5):
        raise ValueError("原点格式应为 'lat,N,lon,E[,alt]' æˆ– GGA æŠ¥æ–‡")
    lat_dm = float(parts[0])
    lat_dir = parts[1].upper()
    lon_dm = float(parts[2])
    lon_dir = parts[3].upper()
    alt = float(parts[4]) if len(parts) == 5 else 0.0
    lat = dm_to_decimal(lat_dm)
    lon = dm_to_decimal(lon_dm)
    if lat_dir == "S":
        lat = -lat
    if lon_dir == "W":
        lon = -lon
    return Origin(lat, lon, alt)
def geo_to_ecef(lat_deg: float, lon_deg: float, alt_m: float) -> Tuple[float, float, float]:
    """经纬度 â†’ ECEF。"""
    lat_rad = math.radians(lat_deg)
    lon_rad = math.radians(lon_deg)
    sin_lat = math.sin(lat_rad)
    cos_lat = math.cos(lat_rad)
    sin_lon = math.sin(lon_rad)
    cos_lon = math.cos(lon_rad)
    N = WGS84_A / math.sqrt(1 - WGS84_E2 * sin_lat * sin_lat)
    x = (N + alt_m) * cos_lat * cos_lon
    y = (N + alt_m) * cos_lat * sin_lon
    z = (N * (1 - WGS84_E2) + alt_m) * sin_lat
    return x, y, z
def ecef_to_enu(dx: float, dy: float, dz: float, lat0_rad: float, lon0_rad: float) -> Tuple[float, float, float]:
    """ECEF å·®å€¼ â†’ ENU。"""
    sin_lat = math.sin(lat0_rad)
    cos_lat = math.cos(lat0_rad)
    sin_lon = math.sin(lon0_rad)
    cos_lon = math.cos(lon0_rad)
    east = -sin_lon * dx + cos_lon * dy
    north = -sin_lat * cos_lon * dx - sin_lat * sin_lon * dy + cos_lat * dz
    up = cos_lat * cos_lon * dx + cos_lat * sin_lon * dy + sin_lat * dz
    return east, north, up
def enu_to_ecef(east: float, north: float, up: float, lat0_rad: float, lon0_rad: float) -> Tuple[float, float, float]:
    """ENU â†’ ECEF å·®å€¼ã€‚"""
    sin_lat = math.sin(lat0_rad)
    cos_lat = math.cos(lat0_rad)
    sin_lon = math.sin(lon0_rad)
    cos_lon = math.cos(lon0_rad)
    dx = -sin_lon * east - sin_lat * cos_lon * north + cos_lat * cos_lon * up
    dy = cos_lon * east - sin_lat * sin_lon * north + cos_lat * sin_lon * up
    dz = cos_lat * north + sin_lat * up
    return dx, dy, dz
def ecef_to_lla(x: float, y: float, z: float) -> Tuple[float, float, float]:
    """ECEF â†’ LLA。"""
    p = math.hypot(x, y)
    theta = math.atan2(WGS84_A * z, WGS84_B * p)
    sin_theta = math.sin(theta)
    cos_theta = math.cos(theta)
    lon = math.atan2(y, x)
    lat = math.atan2(
        z + WGS84_EP2 * WGS84_B * sin_theta ** 3,
        p - WGS84_E2 * WGS84_A * cos_theta ** 3,
    )
    N = WGS84_A / math.sqrt(1 - WGS84_E2 * math.sin(lat) ** 2)
    alt = p / math.cos(lat) - N
    return math.degrees(lat), math.degrees(lon), alt
def enu_to_lla(east: float, north: float, up: float, origin: Origin) -> Tuple[float, float, float]:
    """将 ENU åæ ‡è½¬æ¢ä¸ºå®žæ—¶ç»çº¬åº¦ã€‚"""
    dx, dy, dz = enu_to_ecef(east, north, up, origin.latitude_rad, origin.longitude_rad)
    x = origin.ecef[0] + dx
    y = origin.ecef[1] + dy
    z = origin.ecef[2] + dz
    return ecef_to_lla(x, y, z)
def heading_math_to_nav(heading_rad: float) -> float:
    """
    å°†æ•°å­¦åæ ‡ç³» (东为0°, CCW为正) çš„航向角转换为导航坐标系 (北为0°, é¡ºæ—¶é’ˆä¸ºæ­£)。
    """
    heading_deg = math.degrees(heading_rad)
    nav = (90.0 - heading_deg) % 360.0
    if nav < 0:
        nav += 360.0
    return nav
python/hitl/protocols.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,229 @@
"""
与 STM32H7 äº¤äº’的协议封装:
- æž„造 $GPRMI / $GPIMU å¥å­
- è§£æž PythonLink æŽ§åˆ¶å¸§
"""
from __future__ import annotations
import math
import struct
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Callable, Optional, Tuple
GPS_EPOCH = datetime(1980, 1, 6, tzinfo=timezone.utc)
def _ensure_utc(dt: datetime) -> datetime:
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(timezone.utc)
def gps_week_and_tow(timestamp: datetime) -> Tuple[int, float]:
    dt = _ensure_utc(timestamp)
    delta = dt - GPS_EPOCH
    total_seconds = delta.total_seconds()
    week = int(total_seconds // 604800)
    tow = total_seconds - week * 604800
    return week, tow
def format_hhmmss(timestamp: datetime, decimals: int = 3) -> str:
    dt = _ensure_utc(timestamp)
    base = dt.strftime("%H%M%S")
    frac = dt.microsecond / 1_000_000.0
    frac_str = f"{frac:.{decimals}f}".split(".")[1]
    return f"{base}.{frac_str}"
def nmea_checksum(sentence_without_star: str) -> str:
    cs = 0
    for ch in sentence_without_star[1:]:  # è·³è¿‡ $
        cs ^= ord(ch)
    return f"{cs:02X}"
def build_gprmi_sentence(
    timestamp: datetime,
    lat_deg: float,
    lon_deg: float,
    alt_m: float,
    east_vel: float,
    north_vel: float,
    up_vel: float,
    heading_deg: float,
    pitch_deg: float,
    roll_deg: float,
    *,
    lat_std: float = 0.008,
    lon_std: float = 0.008,
    alt_std: float = 0.02,
    vel_std: float = 0.02,
    heading_std: float = 0.1,
    pitch_std: float = 0.1,
    roll_std: float = 0.1,
    baseline_m: float = 1.0,
    sat_count: int = 20,
    ambiguity_count: int = 18,
    quality: int = 4,
) -> bytes:
    timestamp = _ensure_utc(timestamp)
    utc_str = format_hhmmss(timestamp, decimals=2)
    week, tow = gps_week_and_tow(timestamp)
    fields = [
        utc_str,
        str(week),
        f"{tow:.3f}",
        f"{lat_deg:.9f}",
        f"{lon_deg:.9f}",
        f"{alt_m:.3f}",
        f"{lat_std:.3f}",
        f"{lon_std:.3f}",
        f"{alt_std:.3f}",
        f"{east_vel:.3f}",
        f"{north_vel:.3f}",
        f"{up_vel:.3f}",
        f"{vel_std:.3f}",
        f"{heading_deg:.3f}",
        f"{pitch_deg:.3f}",
        f"{roll_deg:.3f}",
        f"{heading_std:.3f}",
        f"{pitch_std:.3f}",
        f"{roll_std:.3f}",
        f"{baseline_m:.3f}",
        str(int(sat_count)),
        str(int(ambiguity_count)),
        str(int(quality)),
    ]
    body = "$GPRMI," + ",".join(fields)
    checksum = nmea_checksum(body)
    sentence = f"{body}*{checksum}\r\n"
    return sentence.encode("ascii")
def build_gpimu_sentence(
    timestamp: datetime,
    accel_g: Tuple[float, float, float],
    gyro_deg_s: Tuple[float, float, float],
    temperature_c: float,
) -> bytes:
    timestamp = _ensure_utc(timestamp)
    utc_str = format_hhmmss(timestamp, decimals=3)
    fields = [
        utc_str,
        f"{accel_g[0]:+.4f}",
        f"{accel_g[1]:+.4f}",
        f"{accel_g[2]:+.4f}",
        f"{gyro_deg_s[0]:+.4f}",
        f"{gyro_deg_s[1]:+.4f}",
        f"{gyro_deg_s[2]:+.4f}",
        f"{temperature_c:.2f}",
    ]
    body = "$GPIMU," + ",".join(fields)
    checksum = nmea_checksum(body)
    sentence = f"{body}*{checksum}\r\n"
    return sentence.encode("ascii")
def _pwm_to_velocity(value: int, center: int = 1500, span: int = 500, max_speed: float = 1.5) -> float:
    ratio = _clamp((value - center) / span, -1.0, 1.0)
    return ratio * max_speed
def _clamp(val: float, min_val: float, max_val: float) -> float:
    return max(min_val, min(max_val, val))
def _decode_payload(payload: bytes) -> Tuple[float, float]:
    if len(payload) == 8:
        forward, turn = struct.unpack("<ff", payload)
        return forward, turn
    if len(payload) == 4:
        steering_pwm, throttle_pwm = struct.unpack("<HH", payload)
        # é‡æ–°æ˜ å°„:转向 PWM â†’ yaw rate;油门 PWM â†’ çº¿é€Ÿåº¦
        forward = _pwm_to_velocity(throttle_pwm)
        turn = math.radians(_pwm_to_velocity(steering_pwm, max_speed=90.0))
        return forward, turn
    raise ValueError(f"不支持的控制负载长度: {len(payload)}")
@dataclass
class PythonLinkFrame:
    forward: float
    turn: float
class PythonLinkDecoder:
    """
    è§£æž PythonLink æŽ§åˆ¶å¸§ (0xAA 0x55 ... 0D 0A)。
    """
    FRAME_HEADER = b"\xAA\x55"
    FRAME_FOOTER = b"\x0D\x0A"
    TYPE_CONTROL = 0x10
    def __init__(self, on_frame: Callable[[PythonLinkFrame], None]):
        self._buffer = bytearray()
        self._on_frame = on_frame
    def feed(self, data: bytes):
        if not data:
            return
        self._buffer.extend(data)
        while True:
            start = self._find_header()
            if start < 0:
                self._buffer.clear()
                return
            if start > 0:
                del self._buffer[:start]
            if len(self._buffer) < 9:
                return
            frame_len = self._expected_frame_length()
            if frame_len is None or len(self._buffer) < frame_len:
                return
            frame = bytes(self._buffer[:frame_len])
            del self._buffer[:frame_len]
            try:
                parsed = self._parse_frame(frame)
                if parsed:
                    self._on_frame(parsed)
            except Exception:
                # ä¸¢å¼ƒæ— æ•ˆå¸§ï¼Œç»§ç»­æœç´¢
                continue
    def _find_header(self) -> int:
        data = bytes(self._buffer)
        idx = data.find(self.FRAME_HEADER)
        return idx
    def _expected_frame_length(self) -> Optional[int]:
        if len(self._buffer) < 5:
            return None
        payload_len = int.from_bytes(self._buffer[3:5], "little")
        return 2 + 1 + 2 + payload_len + 2 + 2  # header + type + len + payload + checksum + footer
    def _parse_frame(self, frame: bytes) -> Optional[PythonLinkFrame]:
        if not (frame.startswith(self.FRAME_HEADER) and frame.endswith(self.FRAME_FOOTER)):
            return None
        frame_type = frame[2]
        if frame_type != self.TYPE_CONTROL:
            return None
        payload_len = int.from_bytes(frame[3:5], "little")
        payload_start = 5
        payload_end = payload_start + payload_len
        payload = frame[payload_start:payload_end]
        checksum_received = int.from_bytes(frame[payload_end:payload_end + 2], "little")
        checksum_calc = sum(frame[2:payload_end]) & 0xFFFF
        if checksum_received != checksum_calc:
            raise ValueError("校验和不匹配")
        forward, turn = _decode_payload(payload)
        return PythonLinkFrame(forward=forward, turn=turn)
python/hitl/simulator.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,303 @@
"""
硬件在环 (HITL) ä»¿çœŸå™¨ï¼šç”Ÿæˆ $GPRMI/$GPIMU ä¼ æ„Ÿå™¨æ•°æ®ï¼Œå¹¶ä¸Ž STM32H7 é€šè¿‡ PythonLink é—­çŽ¯ã€‚
"""
from __future__ import annotations
import dataclasses
import math
import threading
import time
from datetime import datetime, timedelta, timezone
from typing import Callable
import serial
from . import geo
from .dynamics import DifferentialDriveModel, DifferentialDriveState
from .protocols import (
    PythonLinkDecoder,
    PythonLinkFrame,
    build_gpimu_sentence,
    build_gprmi_sentence,
)
@dataclasses.dataclass
class HitlConfig:
    """HITL ä»¿çœŸè¿è¡Œå¿…需的配置项。"""
    uart2_port: str  # STM32 UART2 (GPS/IMU è¾“å…¥ + æŽ§åˆ¶è¾“出)
    uart5_port: str | None  # STM32 UART5 æ—¥å¿—输出
    origin_gga: str
    initial_enu: tuple[float, float, float] = (0.0, 0.0, 0.0)
    initial_heading_deg: float = 0.0
    gps_baudrate: int = 460800
    log_baudrate: int = 921600
    track_width: float = 0.78
    baseline_distance: float = 0.9
    max_linear_speed: float = 2.0
    max_angular_speed_deg: float = 140.0
class SerialEndpoint:
    """线程安全的串口包装。"""
    port: str | None
    baudrate: int
    timeout: float
    _serial: serial.Serial | None
    _lock: threading.Lock
    def __init__(self, port: str | None, baudrate: int, timeout: float = 0.0):
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self._serial = None
        self._lock = threading.Lock()
    def open(self):
        if not self.port:
            return
        if self._serial and self._serial.is_open:
            return
        self._serial = serial.Serial(
            self.port,
            self.baudrate,
            timeout=self.timeout,
            write_timeout=0,
        )
        self._serial.reset_input_buffer()
        self._serial.reset_output_buffer()
    def close(self):
        if self._serial:
            try:
                self._serial.close()
            finally:
                self._serial = None
    def write(self, data: bytes):
        if not data or not self._serial:
            return
        with self._lock:
            _ = self._serial.write(data)
    def read(self, size: int = 1) -> bytes:
        if not self._serial:
            return b""
        try:
            return self._serial.read(size)
        except serial.SerialException:
            return b""
    def readline(self) -> bytes:
        if not self._serial:
            return b""
        try:
            return self._serial.readline()
        except serial.SerialException:
            return b""
class HitlSimulator:
    """电脑仿真环境与 STM32H7 æŽ§åˆ¶æ¿ä¹‹é—´çš„æ¡¥æ¢ã€‚"""
    config: HitlConfig
    origin: geo.Origin
    model: DifferentialDriveModel
    _state_lock: threading.Lock
    _latest_state: DifferentialDriveState
    _target_linear: float
    _target_angular: float
    _sim_time: datetime
    uart2: SerialEndpoint
    log_uart: SerialEndpoint
    _decoder: PythonLinkDecoder
    _running: threading.Event
    _threads: list[threading.Thread]
    on_control: Callable[[float, float], None] | None
    on_log: Callable[[str], None] | None
    def __init__(self, config: HitlConfig):
        self.config = config
        self.origin = geo.parse_origin(config.origin_gga)
        self.model = DifferentialDriveModel(
            track_width=config.track_width,
            max_linear_speed=config.max_linear_speed,
            max_angular_speed=math.radians(config.max_angular_speed_deg),
        )
        self.model.reset(
            east=config.initial_enu[0],
            north=config.initial_enu[1],
            up=config.initial_enu[2],
            heading_deg=config.initial_heading_deg,
        )
        self._state_lock = threading.Lock()
        self._latest_state = self.model.state.copy()
        self._target_linear = 0.0
        self._target_angular = 0.0
        self._sim_time = _initial_timestamp_from_gga(config.origin_gga)
        self.uart2 = SerialEndpoint(config.uart2_port, config.gps_baudrate, timeout=0.0)
        self.log_uart = SerialEndpoint(config.uart5_port, config.log_baudrate, timeout=0.1)
        self._decoder = PythonLinkDecoder(self._handle_control_frame)
        self._running = threading.Event()
        self._threads = []
        self.on_control = None
        self.on_log = None
    # ------------------------------------------------------------------ #
    # ç”Ÿå‘½å‘¨æœŸ
    # ------------------------------------------------------------------ #
    def start(self):
        if self._running.is_set():
            return
        self.uart2.open()
        self.log_uart.open()
        self._running.set()
        self._threads = [
            threading.Thread(target=self._loop_physics, name="hitl-phys", daemon=True),
            threading.Thread(target=self._loop_gprmi, name="hitl-gprmi", daemon=True),
            threading.Thread(target=self._loop_gpimu, name="hitl-gpimu", daemon=True),
            threading.Thread(target=self._loop_control, name="hitl-ctrl", daemon=True),
            threading.Thread(target=self._loop_log, name="hitl-log", daemon=True),
        ]
        for t in self._threads:
            t.start()
    def stop(self):
        if not self._running.is_set():
            return
        self._running.clear()
        for t in self._threads:
            t.join(timeout=1.0)
        self._threads.clear()
        self.uart2.close()
        self.log_uart.close()
    # ------------------------------------------------------------------ #
    # çº¿ç¨‹
    # ------------------------------------------------------------------ #
    def _loop_physics(self):
        last = time.perf_counter()
        while self._running.is_set():
            now = time.perf_counter()
            dt = max(now - last, 1e-4)
            last = now
            with self._state_lock:
                state = self.model.step(self._target_linear, self._target_angular, dt).copy()
                self._latest_state = state
                self._sim_time += timedelta(seconds=dt)
            time.sleep(0.005)
    def _loop_gprmi(self):
        period = 0.1  # 10 Hz
        while self._running.is_set():
            start = time.perf_counter()
            sentence = self._build_gprmi_sentence()
            if sentence:
                self.uart2.write(sentence)
            self._sleep_remaining(start, period)
    def _loop_gpimu(self):
        period = 0.01  # 100 Hz
        while self._running.is_set():
            start = time.perf_counter()
            sentence = self._build_gpimu_sentence()
            if sentence:
                self.uart2.write(sentence)
            self._sleep_remaining(start, period)
    def _loop_control(self):
        while self._running.is_set():
            data = self.uart2.read(128)
            if data:
                self._decoder.feed(data)
            else:
                time.sleep(0.002)
    def _loop_log(self):
        if not self.config.uart5_port:
            while self._running.is_set():
                time.sleep(0.5)
            return
        while self._running.is_set():
            line = self.log_uart.readline()
            if not line:
                time.sleep(0.01)
                continue
            text = line.decode("utf-8", errors="replace").strip()
            if text and self.on_log:
                self.on_log(text)
    # ------------------------------------------------------------------ #
    # æž„造帧
    # ------------------------------------------------------------------ #
    def _build_gprmi_sentence(self) -> bytes | None:
        state, timestamp = self._snapshot()
        lat, lon, alt = geo.enu_to_lla(state.east, state.north, state.up, self.origin)
        heading_nav = geo.heading_math_to_nav(state.heading)
        return build_gprmi_sentence(
            timestamp=timestamp,
            lat_deg=lat,
            lon_deg=lon,
            alt_m=alt,
            east_vel=state.east_velocity,
            north_vel=state.north_velocity,
            up_vel=state.up_velocity,
            heading_deg=heading_nav,
            pitch_deg=state.pitch_deg,
            roll_deg=state.roll_deg,
            baseline_m=self.config.baseline_distance,
        )
    def _build_gpimu_sentence(self) -> bytes | None:
        state, timestamp = self._snapshot()
        return build_gpimu_sentence(
            timestamp=timestamp,
            accel_g=state.body_accel_g,
            gyro_deg_s=state.gyro_deg_s,
            temperature_c=state.temperature_c,
        )
    # ------------------------------------------------------------------ #
    # å·¥å…·
    # ------------------------------------------------------------------ #
    def _snapshot(self) -> tuple[DifferentialDriveState, datetime]:
        with self._state_lock:
            return self._latest_state.copy(), self._sim_time
    def _handle_control_frame(self, frame: PythonLinkFrame):
        with self._state_lock:
            self._target_linear = frame.forward
            self._target_angular = frame.turn
        if self.on_control:
            self.on_control(frame.forward, frame.turn)
    @staticmethod
    def _sleep_remaining(start: float, period: float):
        elapsed = time.perf_counter() - start
        remaining = period - elapsed
        if remaining > 0:
            time.sleep(remaining)
def _initial_timestamp_from_gga(gga: str) -> datetime:
    parts = (gga or "").split(",")
    if len(parts) > 1 and parts[1]:
        time_str = parts[1]
        try:
            hh = int(time_str[0:2])
            mm = int(time_str[2:4])
            ss = int(time_str[4:6])
            frac = float("0." + time_str.split(".")[1]) if "." in time_str else 0.0
            today = datetime.now(timezone.utc).date()
            base = datetime(today.year, today.month, today.day, tzinfo=timezone.utc)
            return base.replace(hour=hh, minute=mm, second=ss, microsecond=int(frac * 1_000_000))
        except (ValueError, IndexError):
            pass
    return datetime.now(timezone.utc)
python/realtime_control.py
@@ -10,10 +10,50 @@
import time
import json
import math
from typing import Optional, Tuple
import threading
import queue
from pathlib import Path
from typing import Callable, Dict, Optional, Tuple
from serial.tools import list_ports
from gps_imu_receiver import GPSIMUReceiver, GPSData, IMUData
from mower_controller import MowerController, wrap_angle
try:
    from PyQt5 import QtCore, QtGui, QtWidgets
except Exception:
    QtWidgets = None
BASE_DIR = Path(__file__).resolve().parent
GUI_STATE_FILE = BASE_DIR / "gui_state.json"
GUI_STATE_DEFAULT = {
    "path_file": "",
    "origin_gga": "",
    "serial_port": "",
}
def load_gui_state() -> Dict[str, str]:
    state = dict(GUI_STATE_DEFAULT)
    try:
        if GUI_STATE_FILE.exists():
            with GUI_STATE_FILE.open('r', encoding='utf-8') as f:
                data = json.load(f)
                if isinstance(data, dict):
                    state.update(data)
    except Exception as exc:
        print(f"[WARN] è¯»å– GUI é…ç½®å¤±è´¥: {exc}")
    return state
def save_gui_state(state: Dict[str, str]) -> None:
    try:
        GUI_STATE_FILE.parent.mkdir(parents=True, exist_ok=True)
        with GUI_STATE_FILE.open('w', encoding='utf-8') as f:
            json.dump(state, f, ensure_ascii=False, indent=2)
    except Exception as exc:
        print(f"[WARN] ä¿å­˜ GUI é…ç½®å¤±è´¥: {exc}")
EARTH_RADIUS_M = 6378137.0
WGS84_A = 6378137.0  # åŠé•¿è½´
@@ -145,6 +185,488 @@
    return east, north, up
if QtWidgets:
    class ZoomableGraphicsView(QtWidgets.QGraphicsView):
        """支持滚轮缩放与拖拽的视图"""
        def __init__(self, scene, parent=None):
            super().__init__(scene, parent)
            self.setRenderHint(QtGui.QPainter.Antialiasing, True)
            self.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
            self.setTransformationAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
            self.setResizeAnchor(QtWidgets.QGraphicsView.AnchorUnderMouse)
            self.setViewportUpdateMode(QtWidgets.QGraphicsView.SmartViewportUpdate)
            self._zoom = 0
        def wheelEvent(self, event: QtGui.QWheelEvent):
            zoom_in_factor = 1.15
            zoom_out_factor = 1 / zoom_in_factor
            if event.angleDelta().y() > 0:
                factor = zoom_in_factor
                self._zoom += 1
            else:
                factor = zoom_out_factor
                self._zoom -= 1
            if self._zoom > 50:
                self._zoom = 50
                return
            if self._zoom < -50:
                self._zoom = -50
                return
            self.scale(factor, factor)
        def reset_zoom(self):
            self._zoom = 0
            self.resetTransform()
    class QtRealtimeDashboard(QtWidgets.QMainWindow):
        """主线程中的 Qt çŠ¶æ€é¢æ¿ï¼Œå®šæ—¶ä»Žé˜Ÿåˆ—å–æ•°æ®åˆ·æ–°"""
        def __init__(
            self,
            controller,
            data_queue: queue.Queue,
            gui_state: Dict[str, str],
            persist_state: Callable[[Dict[str, str]], None],
        ):
            super().__init__()
            self.controller = controller
            self.data_queue = data_queue
            self.gui_state = gui_state or {}
            self.persist_state_cb = persist_state
            self.control_thread: Optional[threading.Thread] = None
            self._last_gui_consume_log = 0.0
            self._fit_applied = False
            self._path_rect = QtCore.QRectF()
            self.path_points = list(controller.path_points or [])
            self.setWindowTitle("Lawnmower è¿åŠ¨æŽ§åˆ¶è°ƒè¯•ç•Œé¢")
            self.resize(1200, 680)
            self._build_ui()
            self.set_path_points(self.path_points, refit=True)
            self._refresh_serial_ports(initial=True)
            self._apply_saved_values()
            self._update_serial_ui()
            self._update_control_buttons()
            self.timer = QtCore.QTimer()
            self.timer.timeout.connect(self._drain_queue)
            self.timer.start(80)  # ~12.5Hz åˆ·æ–°
        def _build_ui(self):
            central = QtWidgets.QWidget()
            layout = QtWidgets.QHBoxLayout(central)
            self.setCentralWidget(central)
            self.scene = QtWidgets.QGraphicsScene()
            self.view = ZoomableGraphicsView(self.scene)
            layout.addWidget(self.view, stretch=3)
            self.path_pen = QtGui.QPen(QtGui.QColor("gray"))
            self.path_pen.setStyle(QtCore.Qt.DashLine)
            self.path_pen.setWidthF(0.8)
            self.path_pen.setCosmetic(True)
            self.path_item = self.scene.addPath(QtGui.QPainterPath(), self.path_pen)
            trail_pen = QtGui.QPen(QtGui.QColor("blue"))
            trail_pen.setWidthF(1.0)
            trail_pen.setCosmetic(True)
            self.trail_path_item = self.scene.addPath(QtGui.QPainterPath(), trail_pen)
            self.heading_item = self.scene.addLine(0, 0, 0, 0, QtGui.QPen(QtGui.QColor("red"), 2))
            robot_pen = QtGui.QPen(QtGui.QColor("red"))
            robot_brush = QtGui.QBrush(QtGui.QColor("red"))
            self.robot_item = self.scene.addEllipse(-0.3, -0.3, 0.6, 0.6, robot_pen, robot_brush)
            target_pen = QtGui.QPen(QtGui.QColor("green"))
            target_brush = QtGui.QBrush(QtGui.QColor("green"))
            self.target_item = self.scene.addEllipse(-0.3, -0.3, 0.6, 0.6, target_pen, target_brush)
            right_panel = QtWidgets.QWidget()
            right_layout = QtWidgets.QVBoxLayout(right_panel)
            right_layout.setContentsMargins(8, 0, 0, 0)
            layout.addWidget(right_panel, stretch=2)
            path_group = QtWidgets.QGroupBox("路径文件")
            path_layout = QtWidgets.QVBoxLayout(path_group)
            path_row = QtWidgets.QHBoxLayout()
            self.path_line = QtWidgets.QLineEdit()
            self.path_browse_btn = QtWidgets.QPushButton("浏览")
            self.path_load_btn = QtWidgets.QPushButton("加载")
            path_row.addWidget(self.path_line, stretch=1)
            path_row.addWidget(self.path_browse_btn)
            path_layout.addLayout(path_row)
            path_layout.addWidget(self.path_load_btn, alignment=QtCore.Qt.AlignRight)
            right_layout.addWidget(path_group)
            origin_group = QtWidgets.QGroupBox("原点 (GGA)")
            origin_layout = QtWidgets.QVBoxLayout(origin_group)
            self.origin_input = QtWidgets.QLineEdit()
            self.origin_update_btn = QtWidgets.QPushButton("更新原点")
            origin_layout.addWidget(self.origin_input)
            origin_layout.addWidget(self.origin_update_btn, alignment=QtCore.Qt.AlignRight)
            right_layout.addWidget(origin_group)
            serial_group = QtWidgets.QGroupBox("串口设置")
            serial_layout = QtWidgets.QVBoxLayout(serial_group)
            serial_row = QtWidgets.QHBoxLayout()
            self.port_combo = QtWidgets.QComboBox()
            self.port_refresh_btn = QtWidgets.QPushButton("刷新")
            serial_row.addWidget(self.port_combo, stretch=1)
            serial_row.addWidget(self.port_refresh_btn)
            serial_layout.addLayout(serial_row)
            self.serial_toggle_btn = QtWidgets.QPushButton("打开串口")
            serial_layout.addWidget(self.serial_toggle_btn)
            right_layout.addWidget(serial_group)
            control_group = QtWidgets.QGroupBox("运动控制")
            control_layout = QtWidgets.QHBoxLayout(control_group)
            self.start_button = QtWidgets.QPushButton("开始")
            self.stop_button = QtWidgets.QPushButton("停止")
            self.stop_button.setEnabled(False)
            control_layout.addWidget(self.start_button)
            control_layout.addWidget(self.stop_button)
            right_layout.addWidget(control_group)
            follow_row = QtWidgets.QHBoxLayout()
            self.follow_checkbox = QtWidgets.QCheckBox("跟随车辆")
            self.follow_checkbox.setChecked(True)
            self.freq_label = QtWidgets.QLabel("控制频率: --.- Hz")
            self.freq_label.setStyleSheet("font-family: Consolas, 'Courier New'; font-size: 12px;")
            follow_row.addWidget(self.follow_checkbox)
            follow_row.addWidget(self.freq_label)
            follow_row.addStretch()
            right_layout.addLayout(follow_row)
            self.info_label = QtWidgets.QLabel()
            self.info_label.setAlignment(QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
            self.info_label.setStyleSheet("font-family: Consolas, 'Courier New'; font-size: 12px;")
            info_scroll = QtWidgets.QScrollArea()
            info_scroll.setWidgetResizable(True)
            info_scroll.setWidget(self.info_label)
            info_scroll.setMinimumWidth(320)
            right_layout.addWidget(info_scroll, stretch=1)
            self.path_browse_btn.clicked.connect(self._browse_path_file)
            self.path_load_btn.clicked.connect(self._load_path_from_ui)
            self.origin_update_btn.clicked.connect(self._update_origin_from_ui)
            self.port_refresh_btn.clicked.connect(lambda: self._refresh_serial_ports(initial=False))
            self.serial_toggle_btn.clicked.connect(self._toggle_serial_connection)
            self.start_button.clicked.connect(self._start_control)
            self.stop_button.clicked.connect(self._stop_control)
        def _apply_saved_values(self):
            path_file = self.gui_state.get("path_file", "")
            if path_file:
                self.path_line.setText(path_file)
            origin = self.gui_state.get("origin_gga", "")
            if origin:
                self.origin_input.setText(origin)
            saved_port = self.gui_state.get("serial_port", "")
            if saved_port:
                index = self.port_combo.findText(saved_port)
                if index >= 0:
                    self.port_combo.setCurrentIndex(index)
        def set_path_points(self, path_points, refit=False):
            self._init_path(path_points or [])
            if refit:
                self._fit_applied = False
                self._ensure_initial_view()
        def _init_path(self, path_points):
            self.path_points = list(path_points or [])
            painter_path = QtGui.QPainterPath()
            if not self.path_points:
                self.path_item.setPath(painter_path)
                self.scene.setSceneRect(-10, -10, 20, 20)
                self._path_rect = self.scene.sceneRect()
                return
            first = True
            for px, py in self.path_points:
                py = -py
                if first:
                    painter_path.moveTo(px, py)
                    first = False
                else:
                    painter_path.lineTo(px, py)
            self.path_item.setPath(painter_path)
            xs = [p[0] for p in self.path_points]
            ys = [-p[1] for p in self.path_points]
            margin = 2.0
            min_x, max_x = min(xs), max(xs)
            min_y, max_y = min(ys), max(ys)
            if min_x == max_x:
                min_x -= 1
                max_x += 1
            if min_y == max_y:
                min_y -= 1
                max_y += 1
            self.scene.setSceneRect(min_x - margin, min_y - margin, (max_x - min_x) + 2 * margin, (max_y - min_y) + 2 * margin)
            self._path_rect = self.scene.sceneRect()
        def _drain_queue(self):
            updated = False
            processed = 0
            while True:
                try:
                    data = self.data_queue.get_nowait()
                except queue.Empty:
                    break
                self._apply_update(data)
                updated = True
                processed += 1
            if updated:
                self.data_queue.task_done()
                now = time.time()
                if now - self._last_gui_consume_log > 1.0:
                    print(f"[GUI] Qt面板刷新 {processed} æ¡æ•°æ®ï¼Œé˜Ÿåˆ—剩余 {self.data_queue.qsize()}")
                    self._last_gui_consume_log = now
        def _apply_update(self, data):
            pos_xyz = data["pos_xyz"]
            heading_deg = data["heading_deg"]
            trail_points = data["trail"]
            target_point = data["target"]
            info_str = data["info_str"]
            control_freq = data.get("control_freq_hz")
            path = QtGui.QPainterPath()
            first = True
            for px, py, _pz in trail_points[-1500:]:
                py = -py
                if first:
                    path.moveTo(px, py)
                    first = False
                else:
                    path.lineTo(px, py)
            self.trail_path_item.setPath(path)
            x = pos_xyz[0]
            y = -pos_xyz[1]
            self.robot_item.setRect(x - 0.3, y - 0.3, 0.6, 0.6)
            arrow_len = 1.0
            heading_rad = math.radians(heading_deg)
            dx = arrow_len * math.cos(heading_rad)
            dy = arrow_len * math.sin(heading_rad)
            self.heading_item.setLine(x, y, x + dx, y - dy)
            if target_point:
                tx = target_point[0]
                ty = -target_point[1]
                self.target_item.setRect(tx - 0.3, ty - 0.3, 0.6, 0.6)
            else:
                self.target_item.setRect(0, 0, 0, 0)
            self.info_label.setText(info_str)
            if self.follow_checkbox.isChecked():
                self._center_on_robot(x, y)
            if control_freq is not None:
                self.freq_label.setText(f"控制频率: {control_freq:5.1f} Hz")
        def _ensure_initial_view(self):
            if not self._fit_applied and not self._path_rect.isNull():
                self.view.reset_zoom()
                self.view.fitInView(self._path_rect, QtCore.Qt.KeepAspectRatio)
                self._fit_applied = True
        def _center_on_robot(self, scene_x, scene_y):
            self.view.centerOn(scene_x, scene_y)
        def _browse_path_file(self):
            current = self.path_line.text().strip()
            start_dir = Path(current).parent if current else BASE_DIR
            file_name, _ = QtWidgets.QFileDialog.getOpenFileName(
                self,
                "选择路径文件",
                str(start_dir),
                "路径文件 (*.txt *.json);;所有文件 (*)",
            )
            if file_name:
                self.path_line.setText(file_name)
        def _load_path_from_ui(self):
            if self.controller.is_running():
                QtWidgets.QMessageBox.warning(self, "路径", "请先停止运动控制,再加载路径。")
                return
            path_file = self.path_line.text().strip()
            if not path_file:
                QtWidgets.QMessageBox.warning(self, "路径", "请先选择路径文件。")
                return
            if not Path(path_file).exists():
                QtWidgets.QMessageBox.warning(self, "路径", "路径文件不存在。")
                return
            if self.controller.load_path(path_file):
                self.set_path_points(self.controller.path_points, refit=True)
                self._save_state({"path_file": path_file})
                QtWidgets.QMessageBox.information(self, "路径", "路径加载成功。")
                self._update_control_buttons()
            else:
                QtWidgets.QMessageBox.warning(self, "路径", "路径加载失败,请检查文件内容。")
        def _update_origin_from_ui(self):
            if self.controller.is_running():
                QtWidgets.QMessageBox.warning(self, "原点", "请先停止运动控制,再更新原点。")
                return
            origin = self.origin_input.text().strip()
            try:
                self.controller.set_origin_geo(origin)
            except ValueError as exc:
                QtWidgets.QMessageBox.warning(self, "原点", f"原点解析失败: {exc}")
                return
            self._save_state({"origin_gga": origin})
            QtWidgets.QMessageBox.information(self, "原点", "原点已更新。")
        def _refresh_serial_ports(self, initial: bool = False):
            ports = [p.device for p in list_ports.comports()]
            saved_port = self.gui_state.get("serial_port", "")
            if initial and saved_port and saved_port not in ports:
                ports.append(saved_port)
            current = self.port_combo.currentText()
            self.port_combo.blockSignals(True)
            self.port_combo.clear()
            self.port_combo.addItems(ports)
            target = saved_port or current
            if target:
                index = self.port_combo.findText(target)
                if index >= 0:
                    self.port_combo.setCurrentIndex(index)
            self.port_combo.blockSignals(False)
        def _toggle_serial_connection(self):
            if self.controller.is_connected():
                if self.controller.is_running():
                    QtWidgets.QMessageBox.warning(self, "串口", "请先停止运动控制,再关闭串口。")
                    return
                self.controller.disconnect()
                QtWidgets.QMessageBox.information(self, "串口", "串口已关闭。")
            else:
                port = self.port_combo.currentText().strip()
                if not port:
                    QtWidgets.QMessageBox.warning(self, "串口", "没有可用串口,请先连接设备。")
                    return
                try:
                    self.controller.set_port(port)
                except RuntimeError as exc:
                    QtWidgets.QMessageBox.warning(self, "串口", str(exc))
                    return
                if not self.controller.connect():
                    QtWidgets.QMessageBox.warning(self, "串口", "串口打开失败,请检查连接。")
                    return
                self._save_state({"serial_port": port})
                QtWidgets.QMessageBox.information(self, "串口", f"串口 {port} å·²æ‰“开。")
            self._update_serial_ui()
            self._update_control_buttons()
        def _start_control(self):
            if self._is_control_running():
                return
            if not self.controller.mower_ctrl:
                QtWidgets.QMessageBox.warning(self, "控制", "请先加载路径文件。")
                return
            if not self.controller.is_connected():
                QtWidgets.QMessageBox.warning(self, "控制", "请先打开串口。")
                return
            self.start_button.setEnabled(False)
            self.stop_button.setEnabled(True)
            def control_entry():
                try:
                    self.controller.run()
                except Exception as exc:
                    print(f"[ERROR] æŽ§åˆ¶çº¿ç¨‹å¼‚常: {exc}")
                    QtCore.QMetaObject.invokeMethod(
                        self,
                        "_show_error_message",
                        QtCore.Qt.QueuedConnection,
                        QtCore.Q_ARG(str, f"控制线程异常: {exc}"),
                    )
                finally:
                    QtCore.QMetaObject.invokeMethod(self, "_on_control_finished", QtCore.Qt.QueuedConnection)
            self.control_thread = threading.Thread(target=control_entry, daemon=True)
            self.control_thread.start()
            self._update_control_buttons()
        def _stop_control(self):
            if not self._is_control_running():
                return
            self.controller.stop()
            self.stop_button.setEnabled(False)
        def _is_control_running(self) -> bool:
            return bool(self.control_thread and self.control_thread.is_alive())
        def _save_state(self, updates: Dict[str, str]):
            self.gui_state.update(updates)
            if self.persist_state_cb:
                self.persist_state_cb(updates)
        def _update_serial_ui(self):
            connected = self.controller.is_connected()
            self.serial_toggle_btn.setText("关闭串口" if connected else "打开串口")
            self.port_combo.setEnabled(not connected)
            self.port_refresh_btn.setEnabled(not connected)
        def _update_control_buttons(self):
            can_start = bool(self.controller.mower_ctrl and self.controller.is_connected() and not self._is_control_running())
            self.start_button.setEnabled(can_start)
            self.stop_button.setEnabled(self._is_control_running())
        @QtCore.pyqtSlot()
        def _on_control_finished(self):
            self.control_thread = None
            self._update_control_buttons()
            self.stop_button.setEnabled(False)
        @QtCore.pyqtSlot(str)
        def _show_error_message(self, message: str):
            QtWidgets.QMessageBox.warning(self, "错误", message)
        def closeEvent(self, event):
            self._stop_control()
            if self.control_thread:
                self.control_thread.join(timeout=3.0)
            if self.controller.is_connected():
                self.controller.disconnect()
            super().closeEvent(event)
else:
    QtRealtimeDashboard = None
class GuiBridge:
    """控制线程向 Qt ä¸»çº¿ç¨‹å‘送数据的桥接器"""
    def __init__(self):
        self.queue: queue.Queue = queue.Queue(maxsize=50)
        self._last_publish_log = 0.0
    def publish(self, data):
        try:
            self.queue.put_nowait(data)
        except queue.Full:
            try:
                self.queue.get_nowait()
            except queue.Empty:
                pass
            try:
                self.queue.put_nowait(data)
            except queue.Full:
                pass
        now = time.time()
        if now - self._last_publish_log > 1.0:
            print(f"[GUI] å‘布数据到队列,当前长度 {self.queue.qsize()},显示位置 {data.get('pos_xyz')}")
            self._last_publish_log = now
class ControlCommandSender:
    """控制命令发送器 - å‘送转向和油门控制信号给STM32"""
    
@@ -229,7 +751,8 @@
    def __init__(self, port: str, baudrate: int = 921600,
                 offset_xyz: Tuple[float, float, float] = (0.0, 0.0, 0.0),
                 origin_geo: Optional[str] = None,
                 origin_alt_m: Optional[float] = None):
                 origin_alt_m: Optional[float] = None,
                 gui_bridge: Optional[GuiBridge] = None):
        """
        åˆå§‹åŒ–实时控制器
        
@@ -243,27 +766,19 @@
        self.port = port
        self.baudrate = baudrate
        self.offset_xyz = offset_xyz
        self.origin_geo = origin_geo or ""
        self.origin_latlon: Optional[Tuple[float, float]] = None
        self.origin_altitude: Optional[float] = origin_alt_m
        self.origin_lat_rad: Optional[float] = None
        self.origin_lon_rad: Optional[float] = None
        self.origin_ecef: Optional[Tuple[float, float, float]] = None
        if origin_geo:
            try:
                lat, lon, alt = _parse_origin_geo(origin_geo)
                self.origin_latlon = (lat, lon)
                # å¦‚果解析出高度,且未手动指定,则优先使用解析出的高度
                if alt is not None and self.origin_altitude is None:
                    self.origin_altitude = alt
                    print(f"[INFO] ä»ŽåŽŸç‚¹é…ç½®ä¸­è§£æžå‡ºé«˜åº¦: {self.origin_altitude:.3f} m")
                print(f"[INFO] åæ ‡åŽŸç‚¹è®¾ç½®: çº¬åº¦={self.origin_latlon[0]:.8f}°, ç»åº¦={self.origin_latlon[1]:.8f}°")
            except ValueError as exc:
                raise ValueError(f"原点坐标解析失败: {exc}") from exc
        try:
            self._apply_origin_geo(self.origin_geo)
        except ValueError as exc:
            raise ValueError(f"原点坐标解析失败: {exc}") from exc
        
        # GPS/IMU接收器
        self.receiver = GPSIMUReceiver(port, baudrate)
        self.receiver: Optional[GPSIMUReceiver] = None
        
        # æŽ§åˆ¶å‘½ä»¤å‘送器 (共用同一个串口)
        self.cmd_sender = None
@@ -280,9 +795,19 @@
        # æ•°æ®ç¼“å­˜
        self.latest_gps: Optional[GPSData] = None
        self.latest_imu: Optional[IMUData] = None
        self.path_points = []
        self.trail_points = []
        self.last_log_time = 0.0
        self.gui_bridge = gui_bridge
        self._stop_event = threading.Event()
        self._last_gui_publish_log = 0.0
        
        # ç»Ÿè®¡
        self.control_count = 0
        self.control_freq_hz = 0.0
        self._freq_sample_time: Optional[float] = None
        self._freq_sample_count = 0
        self._running = False
        
    def load_path(self, path_file: str) -> bool:
        """
@@ -371,6 +896,8 @@
            }
            
            self.mower_ctrl = MowerController(path_points, params=params)
            self.path_points = path_points
            self.trail_points = []
            print("[INFO] è¿åŠ¨æŽ§åˆ¶å™¨åˆå§‹åŒ–å®Œæˆ")
            return True
            
@@ -380,7 +907,15 @@
    
    def connect(self) -> bool:
        """连接串口"""
        if self.is_connected():
            print("[INFO] ä¸²å£å·²è¿žæŽ¥")
            return True
        if not self.port:
            print("[ERROR] æœªæŒ‡å®šä¸²å£ç«¯å£")
            return False
        self.receiver = GPSIMUReceiver(self.port, self.baudrate)
        if not self.receiver.connect():
            self.receiver = None
            return False
        
        # åˆ›å»ºæŽ§åˆ¶å‘½ä»¤å‘送器 (共用串口对象)
@@ -390,7 +925,110 @@
    
    def disconnect(self):
        """断开连接"""
        self.receiver.disconnect()
        if self.receiver:
            self.receiver.disconnect()
            self.receiver = None
        self.cmd_sender = None
    def is_connected(self) -> bool:
        return bool(self.receiver and self.receiver.serial and self.receiver.serial.is_open)
    def is_running(self) -> bool:
        return self._running
    def set_port(self, port: str):
        if self.is_connected():
            raise RuntimeError("请先关闭当前串口,再切换端口")
        self.port = port.strip()
    def stop(self):
        """请求停止控制循环"""
        self._stop_event.set()
    def _update_visualizer(
        self,
        pos_xyz,
        heading_deg: float,
        pitch: float,
        roll: float,
        forward: int,
        turn: int,
        status: str,
        stage: str,
        target_xy: Optional[Tuple[float, float]],
    ):
        if not self.gui_bridge:
            return
        info_lines = [
            f"状态: {status}",
            f"阶段: {stage}",
            f"控制频率: {self.control_freq_hz:5.1f} Hz",
            "",
            "位置 (ENU, m):",
            f"  E: {pos_xyz[0]:+7.2f}",
            f"  N: {pos_xyz[1]:+7.2f}",
            f"  U: {pos_xyz[2]:+7.2f}",
            "",
            "姿态 (deg):",
            f"  Heading: {heading_deg:+7.2f}",
            f"  Pitch  : {pitch:+7.2f}",
            f"  Roll   : {roll:+7.2f}",
            "",
            f"控制输出: F={forward:+4d}, T={turn:+4d}",
        ]
        if target_xy:
            info_lines.append(f"瞄准点: ({target_xy[0]:+.2f}, {target_xy[1]:+.2f})")
        info_str = "\n".join(info_lines)
        if self.gui_bridge:
            trail_snapshot = list(self.trail_points[-1500:])
            payload = {
                "info_str": info_str,
                "pos_xyz": pos_xyz,
                "heading_deg": heading_deg,
                "trail": trail_snapshot,
                "target": target_xy,
                "control_freq_hz": self.control_freq_hz,
            }
            self.gui_bridge.publish(payload)
            now = time.time()
            if now - self._last_gui_publish_log > 1.0:
                print(f"[GUI] æŽ§åˆ¶çº¿ç¨‹æŽ¨é€æ•°æ® pos={pos_xyz}, heading={heading_deg:.2f}, trail={len(trail_snapshot)}, target={target_xy}")
                self._last_gui_publish_log = now
    def _record_control_tick(self, current_time: float):
        if self._freq_sample_time is None:
            self._freq_sample_time = current_time
            self._freq_sample_count = 1
            return
        self._freq_sample_count += 1
        window = current_time - self._freq_sample_time
        if window >= 1.0:
            self.control_freq_hz = self._freq_sample_count / max(window, 1e-6)
            self._freq_sample_count = 0
            self._freq_sample_time = current_time
    def _apply_origin_geo(self, origin_geo: Optional[str]):
        origin_geo = (origin_geo or "").strip()
        self.origin_latlon = None
        self.origin_ecef = None
        self.origin_lat_rad = None
        self.origin_lon_rad = None
        if not origin_geo:
            return
        lat, lon, alt = _parse_origin_geo(origin_geo)
        self.origin_latlon = (lat, lon)
        if alt is not None:
            self.origin_altitude = alt
            print(f"[INFO] ä»ŽåŽŸç‚¹é…ç½®ä¸­è§£æžå‡ºé«˜åº¦: {self.origin_altitude:.3f} m")
        print(f"[INFO] åæ ‡åŽŸç‚¹è®¾ç½®: çº¬åº¦={self.origin_latlon[0]:.8f}°, ç»åº¦={self.origin_latlon[1]:.8f}°")
    def set_origin_geo(self, origin_geo: str):
        """更新原点坐标配置"""
        self.origin_geo = origin_geo.strip()
        self._apply_origin_geo(self.origin_geo)
    
    def _ensure_origin_reference(self, fallback_altitude: float):
        """确保已根据原点经纬度初始化ECEF基准"""
@@ -482,17 +1120,31 @@
        if not self.mower_ctrl:
            print("[ERROR] è¯·å…ˆåŠ è½½è·¯å¾„æ–‡ä»¶")
            return
        if self._running:
            print("[WARN] æŽ§åˆ¶å¾ªçŽ¯å·²åœ¨è¿è¡Œ")
            return
        if not self.is_connected() or not self.receiver:
            print("[ERROR] è¯·å…ˆè¿žæŽ¥ä¸²å£")
            return
        if not self.cmd_sender:
            print("[ERROR] æŽ§åˆ¶å‘½ä»¤å‘送器未就绪")
            return
        self._stop_event.clear()
        self._running = True
        
        print("[INFO] å¼€å§‹å®žæ—¶æŽ§åˆ¶... (按 Ctrl+C åœæ­¢)")
        print("[INFO] ç­‰å¾…GPS数据...")
        
        receiver = self.receiver
        try:
            start_time = time.time()
            last_stats_time = start_time
            self.last_log_time = start_time
            
            # ç­‰å¾…第一个有效的GPS数据
            gps_ready = False
            while not gps_ready:
            while not gps_ready and not self._stop_event.is_set():
                gps_data, imu_data = self.receiver.receive_packet()
                
                if gps_data:
@@ -506,13 +1158,16 @@
                if imu_data:
                    self.latest_imu = imu_data
            
            if self._stop_event.is_set():
                return
            print("[INFO] å¼€å§‹æŽ§åˆ¶å¾ªçޝ")
            
            while True:
            while not self._stop_event.is_set():
                current_time = time.time()
                
                # æŽ¥æ”¶æ•°æ® (非阻塞式,快速轮询)
                gps_data, imu_data = self.receiver.receive_packet()
                gps_data, imu_data = receiver.receive_packet() if receiver else (None, None)
                
                if gps_data:
                    self.latest_gps = gps_data
@@ -527,6 +1182,9 @@
                    if self.latest_gps:
                        # è½¬æ¢GPS数据到本地坐标
                        x, y, z, heading, vx, vy = self._convert_gps_to_local(self.latest_gps)
                        self.trail_points.append((x, y, z))
                        if len(self.trail_points) > 4000:
                            self.trail_points.pop(0)
                        
                        # æ›´æ–°æŽ§åˆ¶å™¨çŠ¶æ€ (只用x,y)
                        self.mower_ctrl.update_gps((x, y), heading, (vx, vy), current_time)
@@ -538,6 +1196,7 @@
                        
                        # è®¡ç®—控制信号
                        control_output = self.mower_ctrl.compute_control(current_time)
                        control_info = control_output.get('info', {})
                        forward_signal = control_output['forward']  # -100~100
                        turn_signal = control_output['turn']        # -100~100
                        
@@ -546,30 +1205,44 @@
                        self.cmd_sender.send_control_command(steering_pwm, throttle_pwm)
                        
                        self.control_count += 1
                        self._record_control_tick(current_time)
                        self.last_control_time = current_time
                        
                        # æ‰“印控制信息 (降低频率)
                        if self.control_count % 74 == 0:  # æ¯ç§’打印一次
                            status = control_output['info'].get('status', 'running')
                            xte = control_output['info'].get('xte', 0)
                        gps_heading = self.latest_gps.heading_angle
                        gps_pitch = self.latest_gps.pitch_angle
                        gps_roll = self.latest_gps.roll_angle
                        # æ‰“印控制信息 (2Hz)
                        if current_time - self.last_log_time >= 0.5:
                            status = control_info.get('status', 'running')
                            xte = control_info.get('xte', 0)
                            # æ•°å­¦è§’度转地理角度:heading_err_deg
                            # æ³¨æ„ control_output['info']['heading_err'] æ˜¯å¼§åº¦
                            heading_err_deg = math.degrees(control_output['info'].get('heading_err', 0))
                            # èŽ·å–æœ€æ–°çš„å§¿æ€è§’ (优先使用 GPS æ•°æ®ï¼ŒIMU ä½œä¸ºè¾…助或高频补充,这里直接用 GPS ç»“构体中的角度)
                            # GPSData ä¸­: heading_angle(0-360), pitch_angle, roll_angle
                            # éƒ½æ˜¯è§’度制
                            gps_heading = self.latest_gps.heading_angle
                            gps_pitch = self.latest_gps.pitch_angle
                            gps_roll = self.latest_gps.roll_angle
                            heading_err_deg = math.degrees(control_info.get('heading_err', 0))
                            print(f"[LOG] POS_ENU: {x:.3f}, {y:.3f}, {z:.3f} | "
                                  f"ATT(H/P/R): {gps_heading:.2f}°, {gps_pitch:.2f}°, {gps_roll:.2f}° | "
                                  f"CTRL: S={status}, F={forward_signal}, T={turn_signal}, Err={xte:.2f}m")
                            self.last_log_time = current_time
                        stage_label = control_info.get('stage', getattr(self.mower_ctrl, 'stage', ''))
                        target_xy = control_info.get('target_xy')
                        self._update_visualizer(
                            (x, y, z),
                            gps_heading,
                            gps_pitch,
                            gps_roll,
                            forward_signal,
                            turn_signal,
                            control_info.get('status', 'running'),
                            stage_label if stage_label else 'unknown',
                            target_xy,
                        )
                        
                        # æ£€æŸ¥æ˜¯å¦å®Œæˆ
                        if control_output['info'].get('status') == 'finished':
                        if control_info.get('status') == 'finished':
                            print("[INFO] è·¯å¾„跟踪完成!")
                            self.stop()
                            break
                
                # ç»Ÿè®¡ä¿¡æ¯ (每10秒)
@@ -592,62 +1265,84 @@
        
        except KeyboardInterrupt:
            print("\n[INFO] ç”¨æˆ·ä¸­æ–­")
            self.stop()
        
        finally:
            self._running = False
            self._stop_event.clear()
            # å‘送停止命令 (中位值)
            print("[INFO] å‘送停止命令...")
            for _ in range(5):
                self.cmd_sender.send_control_command(1500, 1500)
                time.sleep(0.02)
            if self.cmd_sender:
                print("[INFO] å‘送停止命令...")
                for _ in range(5):
                    self.cmd_sender.send_control_command(1500, 1500)
                    time.sleep(0.02)
            
            # æ‰“印最终统计
            print("\n========== æœ€ç»ˆç»Ÿè®¡ ==========")
            print(f"总控制次数: {self.control_count}")
            print(f"GPS数据包: {self.receiver.gps_count}")
            print(f"IMU数据包: {self.receiver.imu_count}")
            print(f"命令发送: {self.cmd_sender.get_stats()}")
            print(f"错误计数: {self.receiver.error_count}")
            if receiver:
                print(f"GPS数据包: {receiver.gps_count}")
                print(f"IMU数据包: {receiver.imu_count}")
            if self.cmd_sender:
                print(f"命令发送: {self.cmd_sender.get_stats()}")
            if receiver:
                print(f"错误计数: {receiver.error_count}")
            print("==============================\n")
def main():
    """主函数"""
    # é…ç½®
    PORT = "COM17"  # æ ¹æ®å®žé™…情况修改
    BAUDRATE = 921600
    PATH_FILE = "gecaolujing2.txt"  # è·¯å¾„文件
    OFFSET_XYZ = (0.0, 0.0, 0.0)  # è½¦è¾†ä¸­å¿ƒç›¸å¯¹GPS天线的ENU偏移(ç±³): (东, åŒ—, å¤©)
    # åŽŸç‚¹ç»çº¬åº¦ (支持 GGA æŠ¥æ–‡æˆ–简略格式)
    # ç¤ºä¾‹GGA: "$GNGGA,060956.700,3949.8890014,N,11616.7555551,E,4,20,0.68,46.621,M,-6.679,M,1.0,0409*7F"
    ORIGIN_GEO = "$GNGGA,060956.700,3949.8890014,N,11616.7555551,E,4,20,0.68,46.621,M,-6.679,M,1.0,0409*7F"
    ORIGIN_ALT = None  # åŽŸç‚¹æµ·æ‹”(ç±³),可选。若GGA报文中包含海拔,会自动使用报文中的海拔。
    # åˆ›å»ºæŽ§åˆ¶å™¨
    DEFAULT_PORT = "COM17"
    DEFAULT_PATH_FILE = "gecaolujing2.txt"
    DEFAULT_ORIGIN_GGA = "$GNGGA,060956.700,3949.8890014,N,11616.7555551,E,4,20,0.68,46.621,M,-6.679,M,1.0,0409*7F"
    OFFSET_XYZ = (0.0, 0.0, 0.0)
    gui_state = load_gui_state()
    selected_port = gui_state.get("serial_port") or DEFAULT_PORT
    origin_geo = gui_state.get("origin_gga") or DEFAULT_ORIGIN_GGA
    path_file = gui_state.get("path_file")
    gui_bridge = GuiBridge() if QtWidgets else None
    controller = RealtimeController(
        PORT,
        selected_port,
        BAUDRATE,
        offset_xyz=OFFSET_XYZ,
        origin_geo=ORIGIN_GEO,
        origin_alt_m=ORIGIN_ALT
        origin_geo=origin_geo,
        origin_alt_m=None,
        gui_bridge=gui_bridge,
    )
    # åŠ è½½è·¯å¾„
    if not controller.load_path(PATH_FILE):
        print("[ERROR] è·¯å¾„加载失败,退出")
        return
    # è¿žæŽ¥ä¸²å£
    if not controller.connect():
        print("[ERROR] ä¸²å£è¿žæŽ¥å¤±è´¥ï¼Œé€€å‡º")
        return
    # è¿è¡ŒæŽ§åˆ¶å¾ªçޝ
    try:
        controller.run()
    finally:
        controller.disconnect()
    # é¢„加载路径文件(若存在)
    candidate_path = path_file or DEFAULT_PATH_FILE
    if candidate_path and Path(candidate_path).exists():
        controller.load_path(candidate_path)
    def persist_state(updates: Dict[str, str]):
        gui_state.update(updates)
        save_gui_state(gui_state)
    if gui_bridge and QtWidgets:
        app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
        dashboard = QtRealtimeDashboard(controller, gui_bridge.queue, gui_state, persist_state)
        dashboard.show()
        try:
            app.exec_()
        finally:
            controller.stop()
            controller.disconnect()
    else:
        # æ— Qt环境时,沿用旧的命令行工作流
        if not controller.path_points and Path(DEFAULT_PATH_FILE).exists():
            controller.load_path(DEFAULT_PATH_FILE)
        if not controller.connect():
            print("[ERROR] ä¸²å£è¿žæŽ¥å¤±è´¥ï¼Œé€€å‡º")
            return
        try:
            controller.run()
        finally:
            controller.disconnect()
if __name__ == "__main__":
python/tests_hitl/__init__.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
"""HITL å­ç³»ç»Ÿæµ‹è¯•包。"""
python/tests_hitl/__pycache__/__init__.cpython-310.pyc
Binary files differ
python/tests_hitl/__pycache__/conftest.cpython-310-pytest-7.1.2.pyc
Binary files differ
python/tests_hitl/__pycache__/test_dynamics.cpython-310-pytest-7.1.2.pyc
Binary files differ
python/tests_hitl/__pycache__/test_geo.cpython-310-pytest-7.1.2.pyc
Binary files differ
python/tests_hitl/__pycache__/test_protocols.cpython-310-pytest-7.1.2.pyc
Binary files differ
python/tests_hitl/__pycache__/test_simulator.cpython-310-pytest-7.1.2.pyc
Binary files differ
python/tests_hitl/conftest.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
"""
pytest é…ç½®ï¼šå°†ä»“库的 python/ ç›®å½•加入 sys.path,便于直接导入 hitl åŒ…。
"""
import sys
from pathlib import Path
PYTHON_DIR = Path(__file__).resolve().parents[1]
if str(PYTHON_DIR) not in sys.path:
    sys.path.insert(0, str(PYTHON_DIR))
python/tests_hitl/test_dynamics.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
import math
import pytest
from hitl.dynamics import DifferentialDriveModel
def test_straight_motion_updates_position():
    model = DifferentialDriveModel()
    model.reset()
    state = model.step(target_linear=1.0, target_angular=0.0, dt=1.0)
    assert state.east == pytest.approx(1.0, rel=0.05)
    assert state.north == pytest.approx(0.0, abs=0.05)
    assert state.heading == pytest.approx(0.0, abs=1e-3)
def test_in_place_rotation_changes_heading():
    model = DifferentialDriveModel()
    model.reset()
    state = model.step(target_linear=0.0, target_angular=math.radians(90.0), dt=1.0)
    assert math.degrees(state.heading) == pytest.approx(90.0, abs=1.0)
    assert abs(state.linear_velocity) < 1e-6
def test_velocity_limits_respected():
    model = DifferentialDriveModel(max_linear_speed=0.5, max_linear_accel=0.5)
    model.reset()
    state = model.step(target_linear=2.0, target_angular=0.0, dt=0.2)
    assert state.linear_velocity <= model.max_linear_speed + 1e-6
def test_body_acceleration_contains_gravity():
    model = DifferentialDriveModel()
    model.reset()
    state = model.step(target_linear=0.0, target_angular=0.0, dt=0.1)
    ax, ay, az = state.body_accel_g
    assert -1.05 < az < -0.95  # é‡åŠ›æ–¹å‘
    assert abs(ax) < 0.05
    assert abs(ay) < 0.05
python/tests_hitl/test_geo.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
import math
import pytest
from hitl import geo
GGA_SAMPLE = "$GNGGA,080112.000,3949.8105069,N,11616.6876082,E,4,44,0.42,48.502,M,-6.684,M,1.0,0409*73"
def test_parse_origin_from_gga():
    origin = geo.parse_origin(GGA_SAMPLE)
    assert origin.latitude_deg == pytest.approx(39.830175115)
    assert origin.longitude_deg == pytest.approx(116.278126803)
    assert origin.altitude_m == pytest.approx(48.502)
def test_parse_origin_short_format():
    origin = geo.parse_origin("3949.8105,N,11616.6876,E,50.0")
    assert origin.latitude_deg == pytest.approx(39.830175)
    assert origin.longitude_deg == pytest.approx(116.278126)
    assert origin.altitude_m == pytest.approx(50.0)
def test_enu_roundtrip_accuracy():
    origin = geo.parse_origin(GGA_SAMPLE)
    east, north, up = 5.0, -3.0, 1.2
    lat, lon, alt = geo.enu_to_lla(east, north, up, origin)
    x, y, z = geo.geo_to_ecef(lat, lon, alt)
    dx = x - origin.ecef[0]
    dy = y - origin.ecef[1]
    dz = z - origin.ecef[2]
    east_rt, north_rt, up_rt = geo.ecef_to_enu(dx, dy, dz, origin.latitude_rad, origin.longitude_rad)
    assert east_rt == pytest.approx(east, abs=0.01)
    assert north_rt == pytest.approx(north, abs=0.01)
    assert up_rt == pytest.approx(up, abs=0.01)
def test_heading_math_to_nav():
    assert geo.heading_math_to_nav(math.radians(0.0)) == pytest.approx(90.0)
    assert geo.heading_math_to_nav(math.radians(90.0)) == pytest.approx(0.0)
    assert geo.heading_math_to_nav(math.radians(-90.0)) == pytest.approx(180.0)
python/tests_hitl/test_protocols.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,85 @@
import struct
from datetime import datetime, timezone
import pytest
from hitl import protocols
def _calc_checksum(sentence: str) -> str:
    cs = 0
    for ch in sentence[1:]:
        cs ^= ord(ch)
    return f"{cs:02X}"
def test_build_gprmi_sentence_format():
    ts = datetime(2025, 11, 21, 8, 1, 12, tzinfo=timezone.utc)
    sentence = protocols.build_gprmi_sentence(
        timestamp=ts,
        lat_deg=39.8301751,
        lon_deg=116.2781268,
        alt_m=48.5,
        east_vel=0.2,
        north_vel=0.1,
        up_vel=0.0,
        heading_deg=123.4,
        pitch_deg=1.2,
        roll_deg=-0.6,
    ).decode("ascii")
    assert sentence.startswith("$GPRMI,")
    assert sentence.endswith("\r\n")
    body, checksum = sentence.strip()[0:sentence.index("*")], sentence[sentence.index("*") + 1 : sentence.index("*") + 3]
    assert checksum == _calc_checksum(body)
    fields = body.split(",")
    assert len(fields) == 24  # $GPRMI + 23 ä¸ªå­—段
def test_build_gpimu_sentence_format():
    ts = datetime(2025, 11, 21, 8, 1, 12, tzinfo=timezone.utc)
    sentence = protocols.build_gpimu_sentence(
        timestamp=ts,
        accel_g=(0.01, -0.02, -0.99),
        gyro_deg_s=(0.1, 0.2, -0.3),
        temperature_c=29.5,
    ).decode("ascii")
    assert sentence.startswith("$GPIMU,")
    body = sentence.strip()[0:sentence.index("*")]
    checksum = sentence[sentence.index("*") + 1 : sentence.index("*") + 3]
    assert checksum == _calc_checksum(body)
    assert len(body.split(",")) == 9  # $GPIMU + 8 å­—段
def _build_control_frame(payload: bytes) -> bytes:
    header = b"\xAA\x55"
    frame_type = b"\x10"
    length = struct.pack("<H", len(payload))
    checksum = (sum(frame_type + length + payload)) & 0xFFFF
    footer = b"\x0D\x0A"
    return header + frame_type + length + payload + struct.pack("<H", checksum) + footer
def test_pythonlink_decoder_float_payload():
    received = []
    decoder = protocols.PythonLinkDecoder(lambda frame: received.append(frame))
    frame = _build_control_frame(struct.pack("<ff", 1.2, -0.4))
    decoder.feed(frame[:5])
    decoder.feed(frame[5:])
    assert len(received) == 1
    assert received[0].forward == pytest.approx(1.2)
    assert received[0].turn == pytest.approx(-0.4)
def test_pythonlink_decoder_pwm_payload():
    received = []
    decoder = protocols.PythonLinkDecoder(lambda frame: received.append(frame))
    frame = _build_control_frame(struct.pack("<HH", 2000, 1600))
    decoder.feed(frame)
    assert len(received) == 1
    assert received[0].forward == pytest.approx(0.3, rel=0.05)  # (1600-1500)/500 * 1.5
    assert received[0].turn == pytest.approx(1.5708, rel=0.05)
python/tests_hitl/test_simulator.py
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
import struct
import time
import pytest
from hitl import simulator as simulator_mod
GGA_SAMPLE = "$GNGGA,080112.000,3949.8105069,N,11616.6876082,E,4,44,0.42,48.502,M,-6.684,M,1.0,0409*73"
def _build_control_frame(payload: bytes) -> bytes:
    header = b"\xAA\x55"
    frame_type = b"\x10"
    length = struct.pack("<H", len(payload))
    checksum = (sum(frame_type + length + payload)) & 0xFFFF
    footer = b"\x0D\x0A"
    return header + frame_type + length + payload + struct.pack("<H", checksum) + footer
class FakeSerialEndpoint:
    def __init__(self, port: str | None, baudrate: int, timeout: float = 0.0):
        self.port = port
        self.baudrate = baudrate
        self.timeout = timeout
        self.opened = False
        self.writes: list[bytes] = []
        self._read_buffer = bytearray()
    def open(self):
        if self.port is None:
            return
        self.opened = True
    def close(self):
        self.opened = False
    def write(self, data: bytes):
        if not data:
            return
        self.writes.append(bytes(data))
    def read(self, size: int = 1) -> bytes:
        if not self._read_buffer:
            return b""
        size = min(size, len(self._read_buffer))
        chunk = self._read_buffer[:size]
        del self._read_buffer[:size]
        return bytes(chunk)
    def readline(self) -> bytes:
        return b""
    def inject(self, data: bytes):
        self._read_buffer.extend(data)
def test_simulator_emits_sensor_frames(monkeypatch):
    monkeypatch.setattr(simulator_mod, "SerialEndpoint", FakeSerialEndpoint)
    config = simulator_mod.HitlConfig(
        uart2_port="SIM",
        uart5_port=None,
        origin_gga=GGA_SAMPLE,
        initial_enu=(0.0, 0.0, 0.0),
    )
    sim = simulator_mod.HitlSimulator(config)
    received_controls: list[tuple[float, float]] = []
    sim.on_control = lambda f, t: received_controls.append((f, t))
    sim.start()
    time.sleep(0.25)
    frame = _build_control_frame(struct.pack("<ff", 0.5, 0.2))
    sim.uart2.inject(frame)
    time.sleep(0.05)
    sim.stop()
    gprmi_count = sum(1 for pkt in sim.uart2.writes if pkt.startswith(b"$GPRMI"))
    gpimu_count = sum(1 for pkt in sim.uart2.writes if pkt.startswith(b"$GPIMU"))
    assert gprmi_count >= 2
    assert gpimu_count >= 10
    assert received_controls, "控制帧未被解析"
    assert received_controls[-1][0] == pytest.approx(0.5, rel=0.1)
    assert received_controls[-1][1] == pytest.approx(0.2, rel=0.1)