diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..f6ff37d
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,90 @@
+name: Build BS2PRO-Controller
+
+on:
+ push:
+ branches: [main, master]
+ pull_request:
+ branches: [main, master]
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: windows-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Set up Go
+ uses: actions/setup-go@v5
+ with:
+ go-version: '1.23'
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Set up .NET SDK
+ uses: actions/setup-dotnet@v4
+ with:
+ dotnet-version: '8.0.x'
+
+ - name: Install Bun
+ uses: oven-sh/setup-bun@v2
+
+ - name: Install Wails
+ run: go install github.com/wailsapp/wails/v2/cmd/wails@latest
+
+ - name: Install go-winres
+ run: go install github.com/tc-hib/go-winres@latest
+
+ - name: Install NSIS
+ run: |
+ choco install nsis -y
+ echo "C:\Program Files (x86)\NSIS" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
+
+ - name: Install frontend dependencies
+ working-directory: frontend
+ run: bun install
+
+ - name: Extract version
+ id: version
+ shell: pwsh
+ run: |
+ $json = Get-Content wails.json | ConvertFrom-Json
+ $ver = $json.info.productVersion
+ echo "VERSION=$ver" >> $env:GITHUB_OUTPUT
+
+ - name: Set LDFLAGS
+ shell: pwsh
+ run: |
+ echo "LDFLAGS=-X github.com/TIANLI0/BS2PRO-Controller/internal/version.BuildVersion=${{ steps.version.outputs.VERSION }} -H=windowsgui" >> $env:GITHUB_ENV
+
+ - name: Build core service
+ run: |
+ go-winres make --in cmd/core/winres/winres.json --out cmd/core/rsrc
+ go build -ldflags "${{ env.LDFLAGS }}" -o build/bin/BS2PRO-Core.exe ./cmd/core/
+
+ - name: Build TempBridge
+ run: |
+ dotnet restore bridge/TempBridge/TempBridge.csproj
+ dotnet publish bridge/TempBridge/TempBridge.csproj -c Release --self-contained false -o build/bin/bridge
+
+ - name: Build main application (Wails + NSIS installer)
+ run: wails build -nsis -ldflags "${{ env.LDFLAGS }}"
+
+ - name: Upload build artifacts
+ uses: actions/upload-artifact@v4
+ with:
+ name: BS2PRO-Controller-${{ steps.version.outputs.VERSION }}
+ path: |
+ build/bin/*.exe
+ build/bin/bridge/
+ retention-days: 30
+
+ - name: Upload installer
+ uses: actions/upload-artifact@v4
+ with:
+ name: BS2PRO-Controller-Installer-${{ steps.version.outputs.VERSION }}
+ path: build/bin/*installer*.exe
+ retention-days: 30
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 91d0ea2..2d4be98 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -60,7 +60,7 @@
"-ExecutionPolicy",
"Bypass",
"-Command",
- "$ErrorActionPreference='Stop'; if (-not (Get-Command riscv-none-elf-objcopy -ErrorAction SilentlyContinue)) { throw '未找到 riscv-none-elf-objcopy,请先安装 RISC-V GNU Toolchain 并加入 PATH。' }; if (-not (Get-Command riscv-none-elf-objdump -ErrorAction SilentlyContinue)) { throw '未找到 riscv-none-elf-objdump,请先安装 RISC-V GNU Toolchain 并加入 PATH。' }; riscv-none-elf-objcopy -I binary -O elf32-littleriscv -B riscv ota/CH591_For_BS2PRO_Ver0.0.3.5.bin ota/fw.elf; riscv-none-elf-objdump -D -M no-aliases ota/fw.elf > ota/fw.asm; Write-Host 'ELF/ASM generated: ota/fw.elf, ota/fw.asm'"
+ "$ErrorActionPreference='Stop'; if (-not (Get-Command riscv-none-elf-objcopy -ErrorAction SilentlyContinue)) { throw 'riscv-none-elf-objcopy not found. Please install RISC-V GNU Toolchain and add it to PATH.' }; if (-not (Get-Command riscv-none-elf-objdump -ErrorAction SilentlyContinue)) { throw 'riscv-none-elf-objdump not found. Please install RISC-V GNU Toolchain and add it to PATH.' }; riscv-none-elf-objcopy -I binary -O elf32-littleriscv -B riscv ota/CH591_For_BS2PRO_Ver0.0.3.5.bin ota/fw.elf; riscv-none-elf-objdump -D -M no-aliases ota/fw.elf > ota/fw.asm; Write-Host 'ELF/ASM generated: ota/fw.elf, ota/fw.asm'"
],
"problemMatcher": []
},
@@ -106,7 +106,7 @@
"-ExecutionPolicy",
"Bypass",
"-Command",
- "$ErrorActionPreference='Stop'; if (-not (Get-Command rizin -ErrorAction SilentlyContinue)) { throw '未找到 rizin,请先安装 rizin 并加入 PATH。' }; rizin -a riscv -b 32 -m 0x0 ota/CH591_For_BS2PRO_Ver0.0.3.5.bin"
+ "$ErrorActionPreference='Stop'; if (-not (Get-Command rizin -ErrorAction SilentlyContinue)) { throw 'rizin not found. Please install rizin and add it to PATH.' }; rizin -a riscv -b 32 -m 0x0 ota/CH591_For_BS2PRO_Ver0.0.3.5.bin"
],
"problemMatcher": []
}
diff --git a/README.md b/README.md
index 8f9174c..36df4e5 100644
--- a/README.md
+++ b/README.md
@@ -1,278 +1,278 @@
# BS2PRO-Controller
-> 飞智空间站 BS2/BS2PRO 的第三方替代控制器
+> A third-party alternative controller for FlyDigi Space Station BS2/BS2PRO
-一个基于 Wails + Go + Next.js 开发的桌面应用,用于控制飞智空间站 BS2/BS2PRO 散热器设备,提供风扇控制、温度监控等功能。
+A desktop application built with Wails + Go + Next.js for controlling FlyDigi Space Station BS2/BS2PRO cooler devices, providing fan control, temperature monitoring, and more.
-## 功能特性
+## Features
-- 🎮 **设备支持**:支持飞智 BS2 和 BS2PRO 散热器
-- 🌡️ **温度监控**:实时监控 CPU/GPU 温度(支持多种温度数据桥接方式)
-- 💨 **风扇控制**:
- - 自动模式:根据温度自动调节风速
- - 学习控温:根据目标温度持续学习并微调曲线偏移
- - 手动模式:自定义固定风速
- - 曲线模式:自定义温度-风速曲线
-- 📊 **可视化面板**:直观的温度和风速实时显示
-- 🎯 **系统托盘**:支持最小化到系统托盘,后台运行
-- 🚀 **开机自启**:可设置开机自动启动并最小化运行
-- 🔧 **多进程架构**:GUI 和核心服务分离,稳定可靠
-- 🛠️ **灯带配置**:支持灯带复杂调控,感谢群友 @Whether
+- **Device Support**: Supports FlyDigi BS2 and BS2PRO coolers
+- **Temperature Monitoring**: Real-time CPU/GPU temperature monitoring (supports multiple temperature data bridging methods)
+- **Fan Control**:
+ - Auto Mode: Automatically adjusts fan speed based on temperature
+ - Learning Temperature Control: Continuously learns and fine-tunes curve offset based on target temperature
+ - Manual Mode: Custom fixed fan speed
+ - Curve Mode: Custom temperature-fan speed curve
+- **Visual Dashboard**: Intuitive real-time temperature and fan speed display
+- **System Tray**: Supports minimizing to system tray for background operation
+- **Auto-start on Boot**: Can be set to auto-start and minimize on boot
+- **Multi-process Architecture**: GUI and core services are separated for stability and reliability
+- **LED Strip Configuration**: Supports complex LED strip control, thanks to community member @Whether
-## 系统架构
+## System Architecture
-项目采用三进程架构:
+The project uses a three-process architecture:
-- **GUI 进程** (`BS2PRO-Controller.exe`):提供用户界面,使用 Wails 框架
-- **核心服务** (`BS2PRO-Core.exe`):后台运行,负责设备通信和温度监控
-- **温度桥接进程** (`TempBridge.exe`):通过 C# 程序获取系统温度数据
+- **GUI Process** (`BS2PRO-Controller.exe`): Provides the user interface, built with the Wails framework
+- **Core Service** (`BS2PRO-Core.exe`): Runs in the background, responsible for device communication and temperature monitoring
+- **Temperature Bridge Process** (`TempBridge.exe`): A C# program that retrieves system temperature data
-三个进程通过 IPC (进程间通信) 进行数据交互。
+The three processes exchange data via IPC (Inter-Process Communication).
-## 技术栈
+## Tech Stack
-### 后端
-- **Go 1.25+**:主要开发语言
-- **Wails v2**:跨平台桌面应用框架
-- **go-hid**:HID 设备通信
-- **zap**:日志记录
+### Backend
+- **Go 1.25+**: Primary development language
+- **Wails v2**: Cross-platform desktop application framework
+- **go-hid**: HID device communication
+- **zap**: Logging
-### 前端
-- **Next.js 16**:React 框架
-- **TypeScript**:类型安全
-- **Tailwind CSS 4**:样式框架
-- **Recharts**:图表可视化
+### Frontend
+- **Next.js 16**: React framework
+- **TypeScript**: Type safety
+- **Tailwind CSS 4**: Styling framework
+- **Recharts**: Chart visualization
-### 温度桥接
-- **C# .NET Framework 4.7.2**:温度数据桥接程序
+### Temperature Bridge
+- **C# .NET Framework 4.7.2**: Temperature data bridge program
-## 开发环境要求
+## Development Environment Requirements
-### 必需软件
-- **Go 1.21+**:[下载地址](https://golang.org/dl/)
-- **Node.js 18+**:[下载地址](https://nodejs.org/)
-- **Bun**:快速的 JavaScript 运行时 [安装说明](https://bun.sh/)
-- **Wails CLI**:安装命令 `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
-- **.NET SDK 8.0+**:[下载地址](https://dotnet.microsoft.com/download)
-- **go-winres**:Windows 资源工具 `go install github.com/tc-hib/go-winres@latest`
+### Required Software
+- **Go 1.21+**: [Download](https://golang.org/dl/)
+- **Node.js 18+**: [Download](https://nodejs.org/)
+- **Bun**: Fast JavaScript runtime [Installation Guide](https://bun.sh/)
+- **Wails CLI**: Install with `go install github.com/wailsapp/wails/v2/cmd/wails@latest`
+- **.NET SDK 8.0+**: [Download](https://dotnet.microsoft.com/download)
+- **go-winres**: Windows resource tool `go install github.com/tc-hib/go-winres@latest`
-### 可选软件
-- **NSIS 3.x**:用于生成安装程序 [下载地址](https://nsis.sourceforge.io/)
+### Optional Software
+- **NSIS 3.x**: For generating installers [Download](https://nsis.sourceforge.io/)
-## 快速开始
+## Quick Start
-### 1. 克隆项目
+### 1. Clone the Project
```bash
git clone https://github.com/TIANLI0/BS2PRO-Controller.git
cd BS2PRO-Controller
```
-### 2. 安装依赖
+### 2. Install Dependencies
-#### 安装 Go 依赖
+#### Install Go Dependencies
```bash
go mod tidy
```
-#### 安装前端依赖
+#### Install Frontend Dependencies
```bash
cd frontend
bun install
cd ..
```
-### 3. 开发模式运行
+### 3. Run in Development Mode
```bash
-# 启动 Wails 开发模式(包含热重载)
+# Start Wails development mode (with hot reload)
wails dev
```
-### 4. 构建生产版本
+### 4. Build Production Version
-#### 构建温度桥接程序
+#### Build Temperature Bridge Program
```bash
build_bridge.bat
```
-#### 构建完整应用
+#### Build Complete Application
```bash
build.bat
```
-构建完成后,可执行文件位于 `build/bin/` 目录:
-- `BS2PRO-Controller.exe` - GUI 主程序
-- `BS2PRO-Core.exe` - 核心服务
-- `bridge/TempBridge.exe` - 温度桥接程序
+After building, the executables are located in the `build/bin/` directory:
+- `BS2PRO-Controller.exe` - GUI main program
+- `BS2PRO-Core.exe` - Core service
+- `bridge/TempBridge.exe` - Temperature bridge program
-安装程序位于 `build/bin/` 目录:
-- `BS2PRO-Controller-amd64-installer.exe` - Windows 安装程序
+The installer is located in the `build/bin/` directory:
+- `BS2PRO-Controller-amd64-installer.exe` - Windows installer
-## 项目结构
+## Project Structure
```
BS2PRO-Controller/
-├── main.go # GUI 主程序入口
-├── app.go # GUI 应用逻辑
-├── wails.json # Wails 配置文件
-├── build.bat # Windows 构建脚本
-├── build_bridge.bat # 桥接程序构建脚本
+├── main.go # GUI main program entry point
+├── app.go # GUI application logic
+├── wails.json # Wails configuration file
+├── build.bat # Windows build script
+├── build_bridge.bat # Bridge program build script
├── cmd/
-│ └── core/ # 核心服务程序
-│ ├── main.go # 服务入口
-│ └── app.go # 服务逻辑
+│ └── core/ # Core service program
+│ ├── main.go # Service entry point
+│ └── app.go # Service logic
│
-├── internal/ # 内部包
-│ ├── autostart/ # 开机自启管理
-│ ├── bridge/ # 温度桥接通信
-│ ├── config/ # 配置管理
-│ ├── device/ # HID 设备通信
-│ ├── ipc/ # 进程间通信
-│ ├── logger/ # 日志模块
-│ ├── temperature/ # 温度监控
-│ ├── tray/ # 系统托盘
-│ ├── types/ # 类型定义
-│ └── version/ # 版本信息
+├── internal/ # Internal packages
+│ ├── autostart/ # Auto-start on boot management
+│ ├── bridge/ # Temperature bridge communication
+│ ├── config/ # Configuration management
+│ ├── device/ # HID device communication
+│ ├── ipc/ # Inter-process communication
+│ ├── logger/ # Logging module
+│ ├── temperature/ # Temperature monitoring
+│ ├── tray/ # System tray
+│ ├── types/ # Type definitions
+│ └── version/ # Version information
│
├── bridge/
-│ └── TempBridge/ # C# 温度桥接程序
-│ └── Program.cs # 桥接程序源码
+│ └── TempBridge/ # C# temperature bridge program
+│ └── Program.cs # Bridge program source code
│
-├── frontend/ # Next.js 前端
+├── frontend/ # Next.js frontend
│ ├── src/
│ │ ├── app/
-│ │ │ ├── components/ # React 组件
-│ │ │ ├── services/ # API 服务
-│ │ │ └── types/ # TypeScript 类型
+│ │ │ ├── components/ # React components
+│ │ │ ├── services/ # API services
+│ │ │ └── types/ # TypeScript types
│ │ └── ...
│ └── package.json
│
-└── build/ # 构建输出目录
+└── build/ # Build output directory
```
-## 使用说明
+## Usage Guide
-### 首次运行
+### First Run
-1. 运行 `BS2PRO-Controller.exe` 启动程序
-2. 程序会自动启动核心服务 `BS2PRO-Core.exe`
-3. 连接你的 BS2/BS2PRO 设备(USB 连接)
-4. 程序会自动检测并连接设备
+1. Run `BS2PRO-Controller.exe` to launch the program
+2. The program will automatically start the core service `BS2PRO-Core.exe`
+3. Connect your BS2/BS2PRO device (USB connection)
+4. The program will automatically detect and connect to the device
-### 风扇控制模式
+### Fan Control Modes
-#### 自动模式
-- 根据当前温度自动调节风速
-- 适合日常使用
+#### Auto Mode
+- Automatically adjusts fan speed based on current temperature
+- Suitable for daily use
-#### 手动模式
-- 设置固定的风速档位(0-9档)
-- 适合特定需求场景
+#### Manual Mode
+- Set a fixed fan speed level (levels 0-9)
+- Suitable for specific use cases
-#### 曲线模式
-- 自定义温度-风速曲线
-- 可添加多个控制点
-- 实现精细化的温度控制
+#### Curve Mode
+- Custom temperature-fan speed curve
+- Multiple control points can be added
+- Achieves fine-grained temperature control
-### 温度监控
+### Temperature Monitoring
-程序支持多种温度监控方式:
+The program supports multiple temperature monitoring methods:
-1. **TempBridge**:通过 C# 桥接程序获取系统温度
+1. **TempBridge**: Retrieves system temperature via the C# bridge program
-### 系统托盘
+### System Tray
-- 点击托盘图标打开主窗口
-- 右键菜单提供快捷操作
-- 支持最小化到托盘后台运行
+- Click the tray icon to open the main window
+- Right-click menu provides quick actions
+- Supports minimizing to tray for background operation
-## 配置文件
+## Configuration File
-配置文件位于 `%APPDATA%\BS2PRO-Controller\config.json`
+The configuration file is located at `%APPDATA%\BS2PRO-Controller\config.json`
-主要配置项:
+Main configuration options:
```json
{
- "autoStart": false, // 开机自启
- "minimizeToTray": true, // 关闭时最小化到托盘
- "temperatureSource": "auto", // 温度数据源
- "updateInterval": 1000, // 更新间隔(毫秒)
- "fanCurve": [...], // 风扇曲线
- "fanMode": "auto" // 风扇模式
+ "autoStart": false, // Auto-start on boot
+ "minimizeToTray": true, // Minimize to tray on close
+ "temperatureSource": "auto", // Temperature data source
+ "updateInterval": 1000, // Update interval (milliseconds)
+ "fanCurve": [...], // Fan curve
+ "fanMode": "auto" // Fan mode
}
```
-## 日志文件
+## Log Files
-日志文件位于 `build/bin/logs/` 目录:
-- `core_YYYYMMDD.log` - 核心服务日志
-- `gui_YYYYMMDD.log` - GUI 程序日志
+Log files are located in the `build/bin/logs/` directory:
+- `core_YYYYMMDD.log` - Core service log
+- `gui_YYYYMMDD.log` - GUI program log
-## 常见问题
+## FAQ
-### 设备无法连接?
-1. 确保 BS2/BS2PRO 设备已正确连接到电脑
-2. 检查设备驱动是否正常安装
-3. 尝试重新插拔设备
-4. 查看日志文件排查具体错误
+### Device won't connect?
+1. Make sure the BS2/BS2PRO device is properly connected to the computer
+2. Check that the device driver is installed correctly
+3. Try unplugging and re-plugging the device
+4. Check the log files for specific errors
-### 温度无法显示?
-1. 检查温度数据源设置
-2. 如使用 TempBridge,确保 `bridge` 目录下的文件完整
-3. 如使用 AIDA64/HWiNFO,确保软件正在运行并开启了共享内存功能
+### Temperature not showing?
+1. Check the temperature data source settings
+2. If using TempBridge, make sure the files in the `bridge` directory are complete
+3. If using AIDA64/HWiNFO, make sure the software is running and shared memory is enabled
-### 开机自启无效?
-1. 以管理员身份运行程序后重新设置
-2. 检查注册表项:`HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`
+### Auto-start on boot not working?
+1. Run the program as administrator and reconfigure the setting
+2. Check the registry entry: `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`
-## 构建说明
+## Build Instructions
-### 版本号管理
+### Version Number Management
-版本号在 `wails.json` 的 `info.productVersion` 字段中定义,构建脚本会自动读取并嵌入到程序中。
+The version number is defined in the `info.productVersion` field of `wails.json`. The build script automatically reads and embeds it into the program.
### LDFLAGS
-构建时会注入版本信息:
+Version information is injected during build:
```bash
--ldflags "-X github.com/TIANLI0/BS2PRO-Controller/internal/version.BuildVersion=版本号 -H=windowsgui"
+-ldflags "-X github.com/TIANLI0/BS2PRO-Controller/internal/version.BuildVersion=VERSION -H=windowsgui"
```
-### 生成安装程序
+### Generating the Installer
-执行 `build.bat` 会自动生成 NSIS 安装程序(需要安装 NSIS)。
+Running `build.bat` will automatically generate an NSIS installer (NSIS must be installed).
-## 贡献指南
+## Contributing
-欢迎提交 Issue 和 Pull Request!
+Issues and Pull Requests are welcome!
-1. Fork 本项目
-2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
-3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
-4. 推送到分支 (`git push origin feature/AmazingFeature`)
-5. 开启 Pull Request
+1. Fork this project
+2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
+3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
+4. Push to the branch (`git push origin feature/AmazingFeature`)
+5. Open a Pull Request
-## 开源许可
+## License
-本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
+This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
-## 作者
+## Author
- **TIANLI0** - [GitHub](https://github.com/TIANLI0)
- Email: wutianli@tianli0.top
-## 致谢
+## Acknowledgements
-- [Wails](https://wails.io/) - 优秀的 Go 桌面应用框架
-- [Next.js](https://nextjs.org/) - React 应用框架
-- 飞智- BS2/BS2PRO 硬件设备
+- [Wails](https://wails.io/) - Excellent Go desktop application framework
+- [Next.js](https://nextjs.org/) - React application framework
+- FlyDigi - BS2/BS2PRO hardware devices
-## 免责声明
+## Disclaimer
-本项目为第三方开源项目,与飞智官方无关。使用本软件产生的任何问题由用户自行承担。
+This is a third-party open-source project and is not affiliated with FlyDigi. Any issues arising from the use of this software are the sole responsibility of the user.
---
-⭐ 如果这个项目对你有帮助,请给一个 Star!
+If this project is helpful to you, please give it a Star!
diff --git a/app.go b/app.go
index 3781ed5..ac0e9df 100644
--- a/app.go
+++ b/app.go
@@ -13,18 +13,18 @@ import (
"go.uber.org/zap"
)
-// App struct - GUI 应用程序结构
+// App struct - GUI application structure
type App struct {
ctx context.Context
ipcClient *ipc.Client
mutex sync.RWMutex
- // 缓存的状态
+ // Cached state
isConnected bool
currentTemp types.TemperatureData
}
-// 为了与前端 API 兼容,重新导出类型
+// Re-export types for frontend API compatibility
type (
FanCurvePoint = types.FanCurvePoint
FanCurveProfile = types.FanCurveProfile
@@ -43,7 +43,7 @@ func init() {
guiLogger = logger.Sugar()
}
-// NewApp 创建 GUI 应用实例
+// NewApp creates a GUI application instance
func NewApp() *App {
return &App{
ipcClient: ipc.NewClient(nil),
@@ -51,32 +51,32 @@ func NewApp() *App {
}
}
-// startup 应用启动时调用
+// startup called when the application starts
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
- guiLogger.Info("=== BS2PRO GUI 启动 ===")
+ guiLogger.Info("=== BS2PRO GUI starting ===")
- // 连接到核心服务
+ // Connect to core service
if err := a.ipcClient.Connect(); err != nil {
- guiLogger.Errorf("连接核心服务失败: %v", err)
- runtime.EventsEmit(ctx, "core-service-error", "无法连接到核心服务")
+ guiLogger.Errorf("Failed to connect to core service: %v", err)
+ runtime.EventsEmit(ctx, "core-service-error", "Unable to connect to core service")
} else {
- guiLogger.Info("已连接到核心服务")
+ guiLogger.Info("Connected to core service")
- // 设置事件处理器
+ // Set event handler
a.ipcClient.SetEventHandler(a.handleCoreEvent)
}
- guiLogger.Info("=== BS2PRO GUI 启动完成 ===")
+ guiLogger.Info("=== BS2PRO GUI startup complete ===")
}
-// GetAppVersion 返回应用版本号(来自版本模块)
+// GetAppVersion returns the application version (from version module)
func (a *App) GetAppVersion() string {
return version.Get()
}
-// handleCoreEvent 处理核心服务推送的事件
+// handleCoreEvent handles events pushed by the core service
func (a *App) handleCoreEvent(event ipc.Event) {
if a.ctx == nil {
return
@@ -147,29 +147,29 @@ func (a *App) handleCoreEvent(event ipc.Event) {
}
}
-// sendRequest 发送请求到核心服务
+// sendRequest sends a request to the core service
func (a *App) sendRequest(reqType ipc.RequestType, data any) (*ipc.Response, error) {
if !a.ipcClient.IsConnected() {
- // 尝试重新连接
+ // Try to reconnect
if err := a.ipcClient.Connect(); err != nil {
- return nil, fmt.Errorf("未连接到核心服务: %v", err)
+ return nil, fmt.Errorf("not connected to core service: %v", err)
}
}
return a.ipcClient.SendRequest(reqType, data)
}
-// === 前端 API 方法 ===
-// 以下所有公开方法保持与原始 app.go 完全兼容
+// === Frontend API Methods ===
+// All public methods below maintain full compatibility with the original app.go
-// ConnectDevice 连接HID设备
+// ConnectDevice connects to the HID device
func (a *App) ConnectDevice() bool {
resp, err := a.sendRequest(ipc.ReqConnect, nil)
if err != nil {
- guiLogger.Errorf("连接设备请求失败: %v", err)
+ guiLogger.Errorf("Connect device request failed: %v", err)
return false
}
if !resp.Success {
- guiLogger.Errorf("连接设备失败: %s", resp.Error)
+ guiLogger.Errorf("Failed to connect device: %s", resp.Error)
return false
}
var success bool
@@ -177,12 +177,12 @@ func (a *App) ConnectDevice() bool {
return success
}
-// DisconnectDevice 断开设备连接
+// DisconnectDevice disconnects the device
func (a *App) DisconnectDevice() {
a.sendRequest(ipc.ReqDisconnect, nil)
}
-// GetDeviceStatus 获取设备连接状态
+// GetDeviceStatus gets the device connection status
func (a *App) GetDeviceStatus() map[string]any {
resp, err := a.sendRequest(ipc.ReqGetDeviceStatus, nil)
if err != nil {
@@ -196,15 +196,15 @@ func (a *App) GetDeviceStatus() map[string]any {
return status
}
-// GetConfig 获取当前配置
+// GetConfig gets the current configuration
func (a *App) GetConfig() AppConfig {
resp, err := a.sendRequest(ipc.ReqGetConfig, nil)
if err != nil {
- guiLogger.Errorf("获取配置失败: %v", err)
+ guiLogger.Errorf("Failed to get config: %v", err)
return types.GetDefaultConfig(false)
}
if !resp.Success {
- guiLogger.Errorf("获取配置失败: %s", resp.Error)
+ guiLogger.Errorf("Failed to get config: %s", resp.Error)
return types.GetDefaultConfig(false)
}
var cfg AppConfig
@@ -212,7 +212,7 @@ func (a *App) GetConfig() AppConfig {
return cfg
}
-// UpdateConfig 更新配置
+// UpdateConfig updates the configuration
func (a *App) UpdateConfig(cfg AppConfig) error {
resp, err := a.sendRequest(ipc.ReqUpdateConfig, cfg)
if err != nil {
@@ -224,7 +224,7 @@ func (a *App) UpdateConfig(cfg AppConfig) error {
return nil
}
-// SetFanCurve 设置风扇曲线
+// SetFanCurve sets the fan curve
func (a *App) SetFanCurve(curve []FanCurvePoint) error {
resp, err := a.sendRequest(ipc.ReqSetFanCurve, curve)
if err != nil {
@@ -236,7 +236,7 @@ func (a *App) SetFanCurve(curve []FanCurvePoint) error {
return nil
}
-// GetFanCurve 获取风扇曲线
+// GetFanCurve gets the fan curve
func (a *App) GetFanCurve() []FanCurvePoint {
resp, err := a.sendRequest(ipc.ReqGetFanCurve, nil)
if err != nil {
@@ -250,7 +250,7 @@ func (a *App) GetFanCurve() []FanCurvePoint {
return curve
}
-// GetFanCurveProfiles 获取曲线方案列表
+// GetFanCurveProfiles gets the list of curve profiles
func (a *App) GetFanCurveProfiles() FanCurveProfilesPayload {
resp, err := a.sendRequest(ipc.ReqGetFanCurveProfiles, nil)
if err != nil || !resp.Success {
@@ -265,7 +265,7 @@ func (a *App) GetFanCurveProfiles() FanCurveProfilesPayload {
return payload
}
-// SetActiveFanCurveProfile 设置当前激活曲线方案
+// SetActiveFanCurveProfile sets the currently active curve profile
func (a *App) SetActiveFanCurveProfile(profileID string) error {
resp, err := a.sendRequest(ipc.ReqSetActiveFanCurveProfile, ipc.SetActiveFanCurveProfileParams{ID: profileID})
if err != nil {
@@ -277,7 +277,7 @@ func (a *App) SetActiveFanCurveProfile(profileID string) error {
return nil
}
-// SaveFanCurveProfile 保存曲线方案
+// SaveFanCurveProfile saves a curve profile
func (a *App) SaveFanCurveProfile(profileID, name string, curve []FanCurvePoint, setActive bool) (FanCurveProfile, error) {
resp, err := a.sendRequest(ipc.ReqSaveFanCurveProfile, ipc.SaveFanCurveProfileParams{
ID: profileID,
@@ -296,7 +296,7 @@ func (a *App) SaveFanCurveProfile(profileID, name string, curve []FanCurvePoint,
return profile, nil
}
-// DeleteFanCurveProfile 删除曲线方案
+// DeleteFanCurveProfile deletes a curve profile
func (a *App) DeleteFanCurveProfile(profileID string) error {
resp, err := a.sendRequest(ipc.ReqDeleteFanCurveProfile, ipc.DeleteFanCurveProfileParams{ID: profileID})
if err != nil {
@@ -308,7 +308,7 @@ func (a *App) DeleteFanCurveProfile(profileID string) error {
return nil
}
-// ExportFanCurveProfiles 导出曲线方案
+// ExportFanCurveProfiles exports curve profiles
func (a *App) ExportFanCurveProfiles() (string, error) {
resp, err := a.sendRequest(ipc.ReqExportFanCurveProfiles, nil)
if err != nil {
@@ -322,7 +322,7 @@ func (a *App) ExportFanCurveProfiles() (string, error) {
return code, nil
}
-// ImportFanCurveProfiles 导入曲线方案
+// ImportFanCurveProfiles imports curve profiles
func (a *App) ImportFanCurveProfiles(code string) error {
resp, err := a.sendRequest(ipc.ReqImportFanCurveProfiles, ipc.ImportFanCurveProfilesParams{Code: code})
if err != nil {
@@ -334,7 +334,7 @@ func (a *App) ImportFanCurveProfiles(code string) error {
return nil
}
-// SetAutoControl 设置智能变频
+// SetAutoControl sets smart fan control
func (a *App) SetAutoControl(enabled bool) error {
resp, err := a.sendRequest(ipc.ReqSetAutoControl, ipc.SetAutoControlParams{Enabled: enabled})
if err != nil {
@@ -346,7 +346,7 @@ func (a *App) SetAutoControl(enabled bool) error {
return nil
}
-// SetManualGear 设置手动挡位
+// SetManualGear sets the manual gear
func (a *App) SetManualGear(gear, level string) bool {
resp, err := a.sendRequest(ipc.ReqSetManualGear, ipc.SetManualGearParams{Gear: gear, Level: level})
if err != nil {
@@ -360,7 +360,7 @@ func (a *App) SetManualGear(gear, level string) bool {
return success
}
-// GetAvailableGears 获取可用挡位
+// GetAvailableGears gets available gears
func (a *App) GetAvailableGears() map[string][]GearCommand {
resp, err := a.sendRequest(ipc.ReqGetAvailableGears, nil)
if err != nil {
@@ -374,13 +374,13 @@ func (a *App) GetAvailableGears() map[string][]GearCommand {
return gears
}
-// ManualSetFanSpeed 废弃方法
+// ManualSetFanSpeed deprecated method
func (a *App) ManualSetFanSpeed(rpm int) bool {
- guiLogger.Warn("ManualSetFanSpeed 已废弃,请使用 SetManualGear")
+ guiLogger.Warn("ManualSetFanSpeed is deprecated, use SetManualGear instead")
return false
}
-// SetCustomSpeed 设置自定义转速
+// SetCustomSpeed sets custom fan speed
func (a *App) SetCustomSpeed(enabled bool, rpm int) error {
resp, err := a.sendRequest(ipc.ReqSetCustomSpeed, ipc.SetCustomSpeedParams{Enabled: enabled, RPM: rpm})
if err != nil {
@@ -392,7 +392,7 @@ func (a *App) SetCustomSpeed(enabled bool, rpm int) error {
return nil
}
-// SetGearLight 设置挡位灯
+// SetGearLight sets the gear indicator light
func (a *App) SetGearLight(enabled bool) bool {
resp, err := a.sendRequest(ipc.ReqSetGearLight, ipc.SetBoolParams{Enabled: enabled})
if err != nil {
@@ -403,7 +403,7 @@ func (a *App) SetGearLight(enabled bool) bool {
return success
}
-// SetPowerOnStart 设置通电自启动
+// SetPowerOnStart sets power-on auto-start
func (a *App) SetPowerOnStart(enabled bool) bool {
resp, err := a.sendRequest(ipc.ReqSetPowerOnStart, ipc.SetBoolParams{Enabled: enabled})
if err != nil {
@@ -414,7 +414,7 @@ func (a *App) SetPowerOnStart(enabled bool) bool {
return success
}
-// SetSmartStartStop 设置智能启停
+// SetSmartStartStop sets smart start/stop
func (a *App) SetSmartStartStop(mode string) bool {
resp, err := a.sendRequest(ipc.ReqSetSmartStartStop, ipc.SetStringParams{Value: mode})
if err != nil {
@@ -425,7 +425,7 @@ func (a *App) SetSmartStartStop(mode string) bool {
return success
}
-// SetBrightness 设置亮度
+// SetBrightness sets the brightness
func (a *App) SetBrightness(percentage int) bool {
resp, err := a.sendRequest(ipc.ReqSetBrightness, ipc.SetIntParams{Value: percentage})
if err != nil {
@@ -436,7 +436,7 @@ func (a *App) SetBrightness(percentage int) bool {
return success
}
-// SetLightStrip 设置灯带
+// SetLightStrip sets the light strip
func (a *App) SetLightStrip(cfg types.LightStripConfig) error {
resp, err := a.sendRequest(ipc.ReqSetLightStrip, ipc.SetLightStripParams{Config: cfg})
if err != nil {
@@ -448,7 +448,7 @@ func (a *App) SetLightStrip(cfg types.LightStripConfig) error {
return nil
}
-// GetTemperature 获取当前温度
+// GetTemperature gets the current temperature
func (a *App) GetTemperature() TemperatureData {
resp, err := a.sendRequest(ipc.ReqGetTemperature, nil)
if err != nil {
@@ -461,7 +461,7 @@ func (a *App) GetTemperature() TemperatureData {
return temp
}
-// GetCurrentFanData 获取当前风扇数据
+// GetCurrentFanData gets the current fan data
func (a *App) GetCurrentFanData() *FanData {
resp, err := a.sendRequest(ipc.ReqGetCurrentFanData, nil)
if err != nil {
@@ -474,7 +474,7 @@ func (a *App) GetCurrentFanData() *FanData {
return &fanData
}
-// TestTemperatureReading 测试温度读取
+// TestTemperatureReading tests temperature reading
func (a *App) TestTemperatureReading() TemperatureData {
resp, err := a.sendRequest(ipc.ReqTestTemperatureReading, nil)
if err != nil {
@@ -485,7 +485,7 @@ func (a *App) TestTemperatureReading() TemperatureData {
return temp
}
-// TestBridgeProgram 测试桥接程序
+// TestBridgeProgram tests the bridge program
func (a *App) TestBridgeProgram() BridgeTemperatureData {
resp, err := a.sendRequest(ipc.ReqTestBridgeProgram, nil)
if err != nil {
@@ -496,7 +496,7 @@ func (a *App) TestBridgeProgram() BridgeTemperatureData {
return data
}
-// GetBridgeProgramStatus 获取桥接程序状态
+// GetBridgeProgramStatus gets the bridge program status
func (a *App) GetBridgeProgramStatus() map[string]any {
resp, err := a.sendRequest(ipc.ReqGetBridgeProgramStatus, nil)
if err != nil {
@@ -507,7 +507,7 @@ func (a *App) GetBridgeProgramStatus() map[string]any {
return status
}
-// SetWindowsAutoStart 设置Windows开机自启动
+// SetWindowsAutoStart sets Windows startup auto-launch
func (a *App) SetWindowsAutoStart(enable bool) error {
resp, err := a.sendRequest(ipc.ReqSetWindowsAutoStart, ipc.SetBoolParams{Enabled: enable})
if err != nil {
@@ -519,7 +519,7 @@ func (a *App) SetWindowsAutoStart(enable bool) error {
return nil
}
-// IsRunningAsAdmin 检查是否以管理员权限运行
+// IsRunningAsAdmin checks if running with administrator privileges
func (a *App) IsRunningAsAdmin() bool {
resp, err := a.sendRequest(ipc.ReqIsRunningAsAdmin, nil)
if err != nil {
@@ -530,7 +530,7 @@ func (a *App) IsRunningAsAdmin() bool {
return isAdmin
}
-// GetAutoStartMethod 获取当前的自启动方式
+// GetAutoStartMethod gets the current auto-start method
func (a *App) GetAutoStartMethod() string {
resp, err := a.sendRequest(ipc.ReqGetAutoStartMethod, nil)
if err != nil {
@@ -541,7 +541,7 @@ func (a *App) GetAutoStartMethod() string {
return method
}
-// SetAutoStartWithMethod 使用指定方式设置自启动
+// SetAutoStartWithMethod sets auto-start using the specified method
func (a *App) SetAutoStartWithMethod(enable bool, method string) error {
resp, err := a.sendRequest(ipc.ReqSetAutoStartWithMethod, ipc.SetAutoStartWithMethodParams{Enable: enable, Method: method})
if err != nil {
@@ -553,7 +553,7 @@ func (a *App) SetAutoStartWithMethod(enable bool, method string) error {
return nil
}
-// CheckWindowsAutoStart 检查Windows开机自启动状态
+// CheckWindowsAutoStart checks Windows startup auto-launch status
func (a *App) CheckWindowsAutoStart() bool {
resp, err := a.sendRequest(ipc.ReqCheckWindowsAutoStart, nil)
if err != nil {
@@ -564,7 +564,7 @@ func (a *App) CheckWindowsAutoStart() bool {
return enabled
}
-// IsAutoStartLaunch 返回当前是否为自启动启动
+// IsAutoStartLaunch returns whether the current launch is an auto-start launch
func (a *App) IsAutoStartLaunch() bool {
resp, err := a.sendRequest(ipc.ReqIsAutoStartLaunch, nil)
if err != nil {
@@ -575,7 +575,7 @@ func (a *App) IsAutoStartLaunch() bool {
return isAutoStart
}
-// ShowWindow 显示主窗口
+// ShowWindow shows the main window
func (a *App) ShowWindow() {
if a.ctx != nil {
runtime.WindowShow(a.ctx)
@@ -583,64 +583,64 @@ func (a *App) ShowWindow() {
}
}
-// HideWindow 隐藏主窗口到托盘
+// HideWindow hides the main window to tray
func (a *App) HideWindow() {
if a.ctx != nil {
runtime.WindowHide(a.ctx)
}
}
-// QuitApp 完全退出应用
+// QuitApp fully quits the application
func (a *App) QuitApp() {
- guiLogger.Info("GUI 请求退出")
+ guiLogger.Info("GUI requesting quit")
- // 关闭 IPC 连接
+ // Close IPC connection
if a.ipcClient != nil {
a.ipcClient.Close()
}
- // 退出 GUI
+ // Quit GUI
if a.ctx != nil {
runtime.Quit(a.ctx)
}
}
-// QuitAll 完全退出应用(包括核心服务)
+// QuitAll fully quits the application (including core service)
func (a *App) QuitAll() {
- guiLogger.Info("GUI 请求完全退出(包括核心服务)")
+ guiLogger.Info("GUI requesting full quit (including core service)")
- // 通知核心服务退出
+ // Notify core service to quit
a.sendRequest(ipc.ReqQuitApp, nil)
- // 关闭 IPC 连接
+ // Close IPC connection
if a.ipcClient != nil {
a.ipcClient.Close()
}
- // 退出 GUI
+ // Quit GUI
if a.ctx != nil {
runtime.Quit(a.ctx)
}
}
-// OnWindowClosing 窗口关闭事件处理
+// OnWindowClosing handles the window close event
func (a *App) OnWindowClosing(ctx context.Context) bool {
- // 返回 false 允许窗口正常关闭并退出 GUI
- // 核心服务会继续在后台运行
+ // Return false to allow normal window close and quit GUI
+ // Core service will continue running in the background
return false
}
-// InitSystemTray 初始化系统托盘(保持API兼容,实际由核心服务处理)
+// InitSystemTray initializes the system tray (kept for API compatibility, actually handled by core service)
func (a *App) InitSystemTray() {
- // 托盘由核心服务管理,GUI 不需要处理
+ // Tray is managed by core service, GUI does not need to handle it
}
-// UpdateGuiResponseTime 更新GUI响应时间(供前端调用)
+// UpdateGuiResponseTime updates the GUI response time (called by frontend)
func (a *App) UpdateGuiResponseTime() {
a.sendRequest(ipc.ReqUpdateGuiResponseTime, nil)
}
-// GetDebugInfo 获取调试信息
+// GetDebugInfo gets debug information
func (a *App) GetDebugInfo() map[string]any {
resp, err := a.sendRequest(ipc.ReqGetDebugInfo, nil)
if err != nil {
@@ -651,7 +651,7 @@ func (a *App) GetDebugInfo() map[string]any {
return info
}
-// SetDebugMode 设置调试模式
+// SetDebugMode sets the debug mode
func (a *App) SetDebugMode(enabled bool) error {
resp, err := a.sendRequest(ipc.ReqSetDebugMode, ipc.SetBoolParams{Enabled: enabled})
if err != nil {
diff --git a/bridge/README.md b/bridge/README.md
index 6409924..31ab1c4 100644
--- a/bridge/README.md
+++ b/bridge/README.md
@@ -1,34 +1,34 @@
-# 温度桥接程序
+# Temperature Bridge Program
-## 概述
+## Overview
-由于Go语言无法直接调用C#库,我们创建了一个C#桥接程序 `TempBridge.exe`,通过 NuGet 引用 `LibreHardwareMonitorLib` 获取准确的CPU和GPU温度数据。
+Since Go cannot directly call C# libraries, we created a C# bridge program `TempBridge.exe` that uses `LibreHardwareMonitorLib` via NuGet to obtain accurate CPU and GPU temperature data.
-当前桥接程序使用 `LibreHardwareMonitorLib >= 0.9.6`,该版本基于 `PawnIO` 能力,不再打包 `WinRing0` 资源。
+The current bridge program uses `LibreHardwareMonitorLib >= 0.9.6`, which is based on `PawnIO` capabilities and no longer bundles `WinRing0` resources.
-## 构建说明
+## Build Instructions
-### 前提条件
+### Prerequisites
-- 安装 [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
-- 可访问 NuGet 源(`dotnet restore` 会自动拉取 `LibreHardwareMonitorLib`)
+- Install [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
+- Access to NuGet sources (`dotnet restore` will automatically fetch `LibreHardwareMonitorLib`)
-### Windows 构建
+### Windows Build
```bash
-# 在项目根目录运行
+# Run from project root directory
build_bridge.bat
```
-### Linux/Mac 构建(交叉编译)
+### Linux/Mac Build (Cross-compilation)
```bash
-# 在项目根目录运行
+# Run from project root directory
chmod +x build_bridge.sh
./build_bridge.sh
```
-### 手动构建
+### Manual Build
```bash
cd bridge/TempBridge
@@ -36,34 +36,34 @@ dotnet restore
dotnet publish TempBridge.csproj -c Release --self-contained false -o ../../build/bin/bridge
```
-## 工作原理
+## How It Works
-1. Go程序调用 `TempBridge.exe`
-2. 桥接程序通过 NuGet 引入的 `LibreHardwareMonitorLib` 读取硬件温度
-3. 桥接程序以JSON格式输出温度数据
-4. Go程序解析JSON数据并使用
+1. The Go program calls `TempBridge.exe`
+2. The bridge program reads hardware temperatures via `LibreHardwareMonitorLib` from NuGet
+3. The bridge program outputs temperature data in JSON format
+4. The Go program parses and uses the JSON data
-## 直接启动排查
+## Direct Launch Diagnostics
-在命令行里直接运行 `TempBridge.exe` 时,程序会进入诊断模式,而不是命名管道模式:
+When running `TempBridge.exe` directly from the command line, the program enters diagnostic mode instead of named pipe mode:
```bash
cd bridge/TempBridge/bin/Release/net472
TempBridge.exe
```
-诊断模式会直接在控制台输出:
+Diagnostic mode outputs directly to the console:
-- CPU/GPU/MAX 温度
-- 当前是否读取成功
-- 错误信息
-- 已发现的温度传感器名称和读数
+- CPU/GPU/MAX temperatures
+- Whether reading was successful
+- Error messages
+- Discovered temperature sensor names and readings
-如果需要强制使用原有的管道模式,可传入 `--pipe` 参数。
+To force the original pipe mode, pass the `--pipe` parameter.
-`--pipe` 模式现在会使用固定命名管道和全局单实例互斥;如果系统里已经有一个 TempBridge 正在监听,新进程不会再启动第二个监听实例,而是直接附着到现有实例。
+`--pipe` mode now uses a fixed named pipe and a global single-instance mutex; if a TempBridge instance is already listening on the system, a new process will not start a second listener but will attach to the existing instance.
-## 输出格式
+## Output Format
```json
{
@@ -76,17 +76,17 @@ TempBridge.exe
}
```
-## 错误处理
+## Error Handling
-如果桥接程序不可用或失败,Go程序会自动回退到原有的温度读取方法:
+If the bridge program is unavailable or fails, the Go program automatically falls back to alternative temperature reading methods:
-1. 使用 `gopsutil` 读取传感器数据
-2. 通过WMI读取Windows系统温度
-3. 使用 `nvidia-smi` 读取NVIDIA GPU温度
+1. Reading sensor data using `gopsutil`
+2. Reading Windows system temperature via WMI
+3. Reading NVIDIA GPU temperature using `nvidia-smi`
-## 注意事项
+## Notes
-- 桥接程序需要以管理员权限运行才能访问所有硬件传感器
-- 首次运行可能需要一些时间来初始化硬件监控
-- 如果遇到权限问题,请尝试以管理员身份运行主程序
-- 运行前请确保系统已安装 `PawnIO`(未安装时 `TempBridge` 会在启动阶段直接报错并退出)
+- The bridge program requires administrator privileges to access all hardware sensors
+- The first run may take some time to initialize hardware monitoring
+- If you encounter permission issues, try running the main program as administrator
+- Ensure `PawnIO` is installed on the system before running (TempBridge will report an error and exit at startup if not installed)
diff --git a/bridge/TempBridge/Program.cs b/bridge/TempBridge/Program.cs
index 562f76f..b28dd7a 100644
--- a/bridge/TempBridge/Program.cs
+++ b/bridge/TempBridge/Program.cs
@@ -74,7 +74,7 @@ static void Main(string[] args)
return;
}
- // 初始化硬件监控
+ // Initialize hardware monitoring
using (var instanceHandle = AcquirePipeInstance())
{
if (instanceHandle == null)
@@ -86,11 +86,11 @@ static void Main(string[] args)
InitializeHardwareMonitor();
- // 输出管道名称,让主程序知道如何连接
+ // Output pipe name so the main program knows how to connect
Console.WriteLine($"PIPE:{PipeName}|OWNER");
Console.Out.Flush();
- // 启动管道服务器
+ // Start pipe server
StartPipeServer();
}
}
@@ -98,8 +98,8 @@ static void Main(string[] args)
{
if (ShouldRunDiagnosticMode(args))
{
- Console.Error.WriteLine("TempBridge 启动失败");
- Console.Error.WriteLine($"错误: {ex.Message}");
+ Console.Error.WriteLine("TempBridge failed to start");
+ Console.Error.WriteLine($"Error: {ex.Message}");
}
else
{
@@ -168,8 +168,8 @@ static bool HasArg(string[] args, string expected)
static void RunConsoleDiagnostics()
{
- Console.WriteLine("TempBridge 诊断模式");
- Console.WriteLine($"时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
+ Console.WriteLine("TempBridge Diagnostic Mode");
+ Console.WriteLine($"Time: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
Console.WriteLine();
InitializeHardwareMonitor();
@@ -187,7 +187,7 @@ static void RunConsoleDiagnostics()
static void PrintTemperatureSummary(TemperatureData data)
{
- Console.WriteLine("温度结果");
+ Console.WriteLine("Temperature Results");
Console.WriteLine($"CPU: {FormatTemperature(data.CpuTemp)}");
Console.WriteLine($"GPU: {FormatTemperature(data.GpuTemp)}");
Console.WriteLine($"MAX: {FormatTemperature(data.MaxTemp)}");
@@ -206,7 +206,7 @@ static string FormatTemperature(int value)
static void PrintHardwareSnapshot()
{
- Console.WriteLine("温度传感器快照");
+ Console.WriteLine("Temperature Sensor Snapshot");
bool foundAny = false;
foreach (IHardware hardware in computer.Hardware)
@@ -216,7 +216,7 @@ static void PrintHardwareSnapshot()
if (!foundAny)
{
- Console.WriteLine("- 未发现可用的温度传感器");
+ Console.WriteLine("- No temperature sensors found");
}
}
@@ -283,9 +283,9 @@ static void EnsurePawnIoInstalled()
if (!PawnIo.IsInstalled)
{
throw new InvalidOperationException(
- "检测到 LibreHardwareMonitor 需要 PawnIO 驱动,但系统未安装。" +
- "请先安装 PawnIO(可从 LibreHardwareMonitor 发布包中的 PawnIO_setup.exe 安装)," +
- "安装完成后重启程序。"
+ "LibreHardwareMonitor requires the PawnIO driver, but it is not installed. " +
+ "Please install PawnIO first (available from PawnIO_setup.exe in the LibreHardwareMonitor release package), " +
+ "then restart the program."
);
}
}
@@ -298,7 +298,7 @@ static void StartPipeServer()
{
using (var pipeServer = new NamedPipeServerStream(PipeName, PipeDirection.InOut))
{
- // 等待客户端连接
+ // Wait for client connection
pipeServer.WaitForConnection();
using (var reader = new StreamReader(pipeServer))
@@ -345,8 +345,8 @@ static void StartPipeServer()
{
if (running)
{
- Console.WriteLine($"管道错误: {ex.Message}");
- Thread.Sleep(1000); // 等待一秒后重试
+ Console.WriteLine($"Pipe error: {ex.Message}");
+ Thread.Sleep(1000); // Wait one second before retrying
}
}
}
@@ -382,7 +382,7 @@ static Response ProcessCommand(Command command)
return new Response
{
Success = false,
- Error = "未知命令类型"
+ Error = "Unknown command type"
};
}
}
@@ -444,7 +444,7 @@ static TemperatureData GetTemperatureData()
if (cpuTemp == 0 && gpuTemp == 0)
{
result.Success = false;
- result.Error = "未读取到有效的 CPU/GPU 温度(PawnIO 可能尚未就绪,请重启软件或重新安装驱动)";
+ result.Error = "No valid CPU/GPU temperature readings (PawnIO may not be ready yet, please restart the software or reinstall the driver)";
}
else
{
diff --git a/build/windows/installer/project.nsi b/build/windows/installer/project.nsi
index 52afd61..64c65f9 100644
--- a/build/windows/installer/project.nsi
+++ b/build/windows/installer/project.nsi
@@ -70,7 +70,7 @@ ManifestDPIAware true
# !define MUI_WELCOMEFINISHPAGE_BITMAP "resources\leftimage.bmp" #Include this to add a bitmap on the left side of the Welcome Page. Must be a size of 164x314
!define MUI_FINISHPAGE_NOAUTOCLOSE # Wait on the INSTFILES page so the user can take a look into the details of the installation steps
!define MUI_FINISHPAGE_RUN "$INSTDIR\${PRODUCT_EXECUTABLE}"
-!define MUI_FINISHPAGE_RUN_TEXT "安装完成后立即启动 BS2PRO 控制器"
+!define MUI_FINISHPAGE_RUN_TEXT "Launch BS2PRO Controller after installation"
!define MUI_ABORTWARNING # This will warn the user if they exit from the installer.
!insertmacro MUI_PAGE_WELCOME # Welcome to the installer page.
@@ -82,14 +82,14 @@ ManifestDPIAware true
!insertmacro MUI_UNPAGE_INSTFILES # Uinstalling page
-!insertmacro MUI_LANGUAGE "SimpChinese" # Set the Language of the installer
+!insertmacro MUI_LANGUAGE "English" # Set the Language of the installer
## The following two statements can be used to sign the installer and the uninstaller. The path to the binaries are provided in %1
#!uninstfinalize 'signtool --file "%1"'
#!finalize 'signtool --file "%1"'
Name "${INFO_PRODUCTNAME}"
-Caption "${INFO_PRODUCTNAME} 安装程序 v${INFO_PRODUCTVERSION}"
+Caption "${INFO_PRODUCTNAME} Installer v${INFO_PRODUCTVERSION}"
BrandingText "${INFO_PRODUCTNAME} v${INFO_PRODUCTVERSION}"
OutFile "..\..\bin\${INFO_PROJECTNAME}-${ARCH}-installer.exe" # Name of the installer's file.
InstallDir "$PROGRAMFILES64\${INFO_PRODUCTNAME}" # Default installing folder (single level)
@@ -102,7 +102,7 @@ Function .onInit
!insertmacro CheckNetFramework 472
Pop $0
${If} $0 == "false"
- MessageBox MB_OK|MB_ICONSTOP "需要 .NET Framework 4.7.2 或更高版本。$\n$\n请先安装 .NET Framework 4.7.2。"
+ MessageBox MB_OK|MB_ICONSTOP ".NET Framework 4.7.2 or later is required.$\n$\nPlease install .NET Framework 4.7.2 first."
Abort
${EndIf}
@@ -115,7 +115,7 @@ FunctionEnd
# Function to clean up legacy/duplicate registry keys
Function CleanLegacyRegistryKeys
- DetailPrint "正在清理历史注册表项..."
+ DetailPrint "Cleaning up legacy registry keys..."
SetRegView 64
# List of known legacy/duplicate registry key names
@@ -129,25 +129,25 @@ Function CleanLegacyRegistryKeys
# Check and remove BS2PRO-controllerBS2PRO-controller
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BS2PRO-controllerBS2PRO-controller" "UninstallString"
${If} $R0 != ""
- DetailPrint "发现重复注册表键: BS2PRO-controllerBS2PRO-controller"
+ DetailPrint "Found duplicate registry key: BS2PRO-controllerBS2PRO-controller"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\BS2PRO-controllerBS2PRO-controller"
- DetailPrint "已删除重复注册表键"
+ DetailPrint "Deleted duplicate registry key"
${EndIf}
# Check and remove TIANLI0BS2PRO-Controller
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TIANLI0BS2PRO-Controller" "UninstallString"
${If} $R0 != ""
- DetailPrint "发现旧版注册表键: TIANLI0BS2PRO-Controller"
+ DetailPrint "Found legacy registry key: TIANLI0BS2PRO-Controller"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TIANLI0BS2PRO-Controller"
- DetailPrint "已删除旧版注册表键"
+ DetailPrint "Deleted legacy registry key"
${EndIf}
# Check and remove TIANLI0BS2PRO (current wails.json would generate this)
ReadRegStr $R0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TIANLI0BS2PRO" "UninstallString"
${If} $R0 != ""
- DetailPrint "发现重复注册表键: TIANLI0BS2PRO"
+ DetailPrint "Found duplicate registry key: TIANLI0BS2PRO"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TIANLI0BS2PRO"
- DetailPrint "已删除重复注册表键"
+ DetailPrint "Deleted duplicate registry key"
${EndIf}
Pop $R1
@@ -156,7 +156,7 @@ FunctionEnd
# Function to detect existing installation and set install directory
Function DetectExistingInstallation
- DetailPrint "正在检查已有安装..."
+ DetailPrint "Checking for existing installation..."
SetRegView 64
Push $R0
@@ -175,9 +175,9 @@ Function DetectExistingInstallation
ReadRegStr $R2 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\TIANLI0BS2PRO" "DisplayVersion"
${EndIf}
${If} $R2 != ""
- DetailPrint "本地已安装版本: $R2"
+ DetailPrint "Locally installed version: $R2"
${Else}
- DetailPrint "本地未检测到已安装版本信息"
+ DetailPrint "No locally installed version detected"
${EndIf}
# First, check all possible registry keys to find installation path
@@ -188,12 +188,12 @@ Function DetectExistingInstallation
${If} $R0 != ""
${If} ${FileExists} "$R0\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R0
- DetailPrint "发现已有安装 (正确键-安装位置): $INSTDIR"
+ DetailPrint "Found existing installation (correct key - install location): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R0\BS2PRO-Core.exe"
StrCpy $INSTDIR $R0
- DetailPrint "发现已有安装 (正确键-安装位置-Core): $INSTDIR"
+ DetailPrint "Found existing installation (correct key - install location - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -206,12 +206,12 @@ Function DetectExistingInstallation
${GetParent} $R0 $R1
${If} ${FileExists} "$R1\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R1
- DetailPrint "发现已有安装 (从正确的注册表键): $INSTDIR"
+ DetailPrint "Found existing installation (from correct registry key): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R1\BS2PRO-Core.exe"
StrCpy $INSTDIR $R1
- DetailPrint "发现已有安装 (从正确的注册表键-Core): $INSTDIR"
+ DetailPrint "Found existing installation (from correct registry key - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -222,12 +222,12 @@ Function DetectExistingInstallation
${If} $R0 != ""
${If} ${FileExists} "$R0\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R0
- DetailPrint "发现旧版安装 (重复键-安装位置): $INSTDIR"
+ DetailPrint "Found legacy installation (duplicate key - install location): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R0\BS2PRO-Core.exe"
StrCpy $INSTDIR $R0
- DetailPrint "发现旧版安装 (重复键-安装位置-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (duplicate key - install location - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -240,12 +240,12 @@ Function DetectExistingInstallation
${GetParent} $R0 $R1
${If} ${FileExists} "$R1\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (重复键): $INSTDIR"
+ DetailPrint "Found legacy installation (duplicate key): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R1\BS2PRO-Core.exe"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (重复键-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (duplicate key - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -259,12 +259,12 @@ Function DetectExistingInstallation
${GetParent} $R0 $R1
${If} ${FileExists} "$R1\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (从图标路径): $INSTDIR"
+ DetailPrint "Found legacy installation (from icon path): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R1\BS2PRO-Core.exe"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (从图标路径-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (from icon path - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -274,12 +274,12 @@ Function DetectExistingInstallation
${If} $R0 != ""
${If} ${FileExists} "$R0\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R0
- DetailPrint "发现旧版安装 (旧格式键-安装位置): $INSTDIR"
+ DetailPrint "Found legacy installation (old format key - install location): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R0\BS2PRO-Core.exe"
StrCpy $INSTDIR $R0
- DetailPrint "发现旧版安装 (旧格式键-安装位置-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (old format key - install location - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -292,12 +292,12 @@ Function DetectExistingInstallation
${GetParent} $R0 $R1
${If} ${FileExists} "$R1\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (旧格式键): $INSTDIR"
+ DetailPrint "Found legacy installation (old format key): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R1\BS2PRO-Core.exe"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (旧格式键-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (old format key - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -307,12 +307,12 @@ Function DetectExistingInstallation
${If} $R0 != ""
${If} ${FileExists} "$R0\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R0
- DetailPrint "发现旧版安装 (TIANLI0BS2PRO-安装位置): $INSTDIR"
+ DetailPrint "Found legacy installation (TIANLI0BS2PRO - install location): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R0\BS2PRO-Core.exe"
StrCpy $INSTDIR $R0
- DetailPrint "发现旧版安装 (TIANLI0BS2PRO-安装位置-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (TIANLI0BS2PRO - install location - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -325,12 +325,12 @@ Function DetectExistingInstallation
${GetParent} $R0 $R1
${If} ${FileExists} "$R1\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (TIANLI0BS2PRO): $INSTDIR"
+ DetailPrint "Found legacy installation (TIANLI0BS2PRO): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R1\BS2PRO-Core.exe"
StrCpy $INSTDIR $R1
- DetailPrint "发现旧版安装 (TIANLI0BS2PRO-Core): $INSTDIR"
+ DetailPrint "Found legacy installation (TIANLI0BS2PRO - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -346,12 +346,12 @@ Function DetectExistingInstallation
${GetParent} $R0 $R1 # Get parent directory
${If} ${FileExists} "$R1\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R1
- DetailPrint "发现已有安装 (从图标): $INSTDIR"
+ DetailPrint "Found existing installation (from icon): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R1\BS2PRO-Core.exe"
StrCpy $INSTDIR $R1
- DetailPrint "发现已有安装 (从图标-Core): $INSTDIR"
+ DetailPrint "Found existing installation (from icon - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -361,12 +361,12 @@ Function DetectExistingInstallation
${If} $R0 != ""
${If} ${FileExists} "$R0\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR $R0
- DetailPrint "发现已有安装 (从安装位置): $INSTDIR"
+ DetailPrint "Found existing installation (from install location): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$R0\BS2PRO-Core.exe"
StrCpy $INSTDIR $R0
- DetailPrint "发现已有安装 (从安装位置-Core): $INSTDIR"
+ DetailPrint "Found existing installation (from install location - Core): $INSTDIR"
Goto found_installation
${EndIf}
${EndIf}
@@ -374,57 +374,57 @@ Function DetectExistingInstallation
# Fourth, check common installation locations (single level path)
${If} ${FileExists} "$PROGRAMFILES64\${INFO_PRODUCTNAME}\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR "$PROGRAMFILES64\${INFO_PRODUCTNAME}"
- DetailPrint "发现已有安装: $INSTDIR"
+ DetailPrint "Found existing installation: $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$PROGRAMFILES32\${INFO_PRODUCTNAME}\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR "$PROGRAMFILES32\${INFO_PRODUCTNAME}"
- DetailPrint "发现已有安装: $INSTDIR"
+ DetailPrint "Found existing installation: $INSTDIR"
Goto found_installation
${EndIf}
# Fifth, check legacy paths with Company\Product structure
${If} ${FileExists} "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR "$PROGRAMFILES64\${INFO_COMPANYNAME}\${INFO_PRODUCTNAME}"
- DetailPrint "发现已有安装 (旧版路径): $INSTDIR"
+ DetailPrint "Found existing installation (legacy path): $INSTDIR"
Goto found_installation
${EndIf}
# Sixth, try alternative common paths
${If} ${FileExists} "$PROGRAMFILES64\BS2PRO-Controller\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR "$PROGRAMFILES64\BS2PRO-Controller"
- DetailPrint "发现已有安装: $INSTDIR"
+ DetailPrint "Found existing installation: $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$PROGRAMFILES32\BS2PRO-Controller\${PRODUCT_EXECUTABLE}"
StrCpy $INSTDIR "$PROGRAMFILES32\BS2PRO-Controller"
- DetailPrint "发现已有安装: $INSTDIR"
+ DetailPrint "Found existing installation: $INSTDIR"
Goto found_installation
${EndIf}
# Seventh, check for BS2PRO-Core.exe in common paths
${If} ${FileExists} "$PROGRAMFILES64\${INFO_PRODUCTNAME}\BS2PRO-Core.exe"
StrCpy $INSTDIR "$PROGRAMFILES64\${INFO_PRODUCTNAME}"
- DetailPrint "发现已有安装 (Core): $INSTDIR"
+ DetailPrint "Found existing installation (Core): $INSTDIR"
Goto found_installation
${EndIf}
${If} ${FileExists} "$PROGRAMFILES64\BS2PRO-Controller\BS2PRO-Core.exe"
StrCpy $INSTDIR "$PROGRAMFILES64\BS2PRO-Controller"
- DetailPrint "发现已有安装 (Core): $INSTDIR"
+ DetailPrint "Found existing installation (Core): $INSTDIR"
Goto found_installation
${EndIf}
# If no existing installation found, use simple product name for directory
# Use BS2PRO-Controller instead of ${INFO_PRODUCTNAME} to ensure consistency
StrCpy $INSTDIR "$PROGRAMFILES64\BS2PRO-Controller"
- DetailPrint "未发现已有安装,使用默认目录: $INSTDIR"
+ DetailPrint "No existing installation found, using default directory: $INSTDIR"
Goto end_detection
found_installation:
- DetailPrint "检测到已有安装 - 将执行升级到: $INSTDIR"
+ DetailPrint "Existing installation detected - will upgrade to: $INSTDIR"
# Now clean up legacy registry keys AFTER we've found the install path
Call CleanLegacyRegistryKeys
@@ -442,7 +442,7 @@ Function WriteCurrentVersionInfo
WriteRegStr HKLM "${UNINST_KEY}" "InstallLocation" "$INSTDIR"
WriteRegStr HKLM "${UNINST_KEY}" "DisplayName" "${INFO_PRODUCTNAME}"
WriteRegStr HKLM "${UNINST_KEY}" "Publisher" "${INFO_COMPANYNAME}"
- DetailPrint "已写入版本信息: ${INFO_PRODUCTVERSION}"
+ DetailPrint "Version info written: ${INFO_PRODUCTVERSION}"
FunctionEnd
# Helper function to trim quotes from a string
@@ -468,7 +468,7 @@ FunctionEnd
# Function to stop running instances and services
Function StopRunningInstances
- DetailPrint "正在检查运行中的进程..."
+ DetailPrint "Checking for running processes..."
# Try to stop the core service first (it manages the fan control)
# Use /FI with proper error handling
@@ -477,7 +477,7 @@ Function StopRunningInstances
Pop $0
Pop $1
${If} $0 == 0
- DetailPrint "已请求关闭 BS2PRO-Core.exe..."
+ DetailPrint "Requested shutdown of BS2PRO-Core.exe..."
Sleep 2000
${EndIf}
@@ -492,7 +492,7 @@ Function StopRunningInstances
Pop $0
Pop $1
${If} $0 == 0
- DetailPrint "已请求关闭 SpaceStationService.exe..."
+ DetailPrint "Requested shutdown of SpaceStationService.exe..."
Sleep 1000
${EndIf}
@@ -507,7 +507,7 @@ Function StopRunningInstances
Pop $0
Pop $1
${If} $0 == 0
- DetailPrint "已请求关闭 ${PRODUCT_EXECUTABLE}..."
+ DetailPrint "Requested shutdown of ${PRODUCT_EXECUTABLE}..."
Sleep 2000
${EndIf}
@@ -536,7 +536,7 @@ Function StopRunningInstances
Call StopBridgeDriver
# Remove scheduled task if exists (ignore errors)
- DetailPrint "正在清理计划任务..."
+ DetailPrint "Cleaning up scheduled tasks..."
nsExec::ExecToStack '"$SYSDIR\schtasks.exe" /delete /tn "BS2PRO-Controller" /f'
Pop $0
Pop $1
@@ -545,15 +545,15 @@ Function StopRunningInstances
Pop $1
# Wait a moment for processes to fully terminate
- DetailPrint "等待进程完全终止..."
+ DetailPrint "Waiting for processes to fully terminate..."
Sleep 2000
- DetailPrint "进程清理完成"
+ DetailPrint "Process cleanup completed"
FunctionEnd
# Function to stop and remove TempBridge kernel driver service
Function StopBridgeDriver
- DetailPrint "正在停止驱动服务 R0TempBridge..."
+ DetailPrint "Stopping driver service R0TempBridge..."
# Stop running driver service (ignore failures if service does not exist)
nsExec::ExecToStack '"$SYSDIR\sc.exe" stop "R0TempBridge"'
@@ -612,7 +612,7 @@ FunctionEnd
# Uninstall-side function (NSIS requires un.* functions in uninstall section)
Function un.StopBridgeDriver
- DetailPrint "正在停止驱动服务 R0TempBridge..."
+ DetailPrint "Stopping driver service R0TempBridge..."
nsExec::ExecToStack '"$SYSDIR\sc.exe" stop "R0TempBridge"'
Pop $0
@@ -666,41 +666,41 @@ FunctionEnd
# Function to backup user data before upgrade
Function BackupUserData
- DetailPrint "正在备份用户配置..."
+ DetailPrint "Backing up user configuration..."
# Backup configuration files if they exist
${If} ${FileExists} "$INSTDIR\config.json"
CopyFiles "$INSTDIR\config.json" "$TEMP\bs2pro_config_backup.json"
- DetailPrint "配置文件已备份"
+ DetailPrint "Configuration file backed up"
${EndIf}
# Backup other important user files if needed
${If} ${FileExists} "$INSTDIR\settings.ini"
CopyFiles "$INSTDIR\settings.ini" "$TEMP\bs2pro_settings_backup.ini"
- DetailPrint "设置文件已备份"
+ DetailPrint "Settings file backed up"
${EndIf}
FunctionEnd
# Function to restore user data after upgrade
Function RestoreUserData
- DetailPrint "正在恢复用户配置..."
+ DetailPrint "Restoring user configuration..."
# Restore configuration files if backup exists
${If} ${FileExists} "$TEMP\bs2pro_config_backup.json"
CopyFiles "$TEMP\bs2pro_config_backup.json" "$INSTDIR\config.json"
- DetailPrint "配置文件已恢复"
+ DetailPrint "Configuration file restored"
${EndIf}
${If} ${FileExists} "$TEMP\bs2pro_settings_backup.ini"
CopyFiles "$TEMP\bs2pro_settings_backup.ini" "$INSTDIR\settings.ini"
Delete "$TEMP\bs2pro_settings_backup.ini" # Clean up backup
- DetailPrint "设置文件已恢复"
+ DetailPrint "Settings file restored"
${EndIf}
FunctionEnd
# Uninstall currently installed PawnIO via bundled installer
Function UninstallPawnIO
- DetailPrint "正在卸载已安装的 PawnIO..."
+ DetailPrint "Uninstalling existing PawnIO..."
${If} ${FileExists} "$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe"
nsExec::ExecToStack /TIMEOUT=60000 '"$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe" -uninstall -silent'
@@ -708,39 +708,39 @@ Function UninstallPawnIO
Pop $1
${If} $0 == "timeout"
- DetailPrint "PawnIO 静默卸载 60 秒未响应,回退到交互卸载..."
+ DetailPrint "PawnIO silent uninstall timed out after 60 seconds, falling back to interactive uninstall..."
nsExec::ExecToStack '"$SYSDIR\taskkill.exe" /F /IM "PawnIO_setup.exe" /T'
Pop $2
Pop $3
ExecWait '"$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe" -uninstall' $0
${If} $0 != 0
- DetailPrint "PawnIO 交互卸载返回码: $0"
+ DetailPrint "PawnIO interactive uninstall return code: $0"
${EndIf}
${ElseIf} $0 == 0
- DetailPrint "PawnIO 卸载完成(静默)"
+ DetailPrint "PawnIO uninstall completed (silent)"
${Else}
- DetailPrint "PawnIO 静默卸载失败,回退到交互卸载..."
+ DetailPrint "PawnIO silent uninstall failed, falling back to interactive uninstall..."
ExecWait '"$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe" -uninstall' $0
${If} $0 != 0
- DetailPrint "PawnIO 交互卸载返回码: $0"
+ DetailPrint "PawnIO interactive uninstall return code: $0"
${EndIf}
${EndIf}
${Else}
- DetailPrint "未找到 PawnIO_setup.exe,跳过卸载程序调用"
+ DetailPrint "PawnIO_setup.exe not found, skipping uninstall call"
${EndIf}
- # 卸载后再做一次驱动服务清理,避免升级残留
+ # Clean up driver services after uninstall to avoid upgrade leftovers
Call StopBridgeDriver
Sleep 1000
FunctionEnd
-Section "主程序 (必需)" SEC_MAIN
+Section "Main Program (Required)" SEC_MAIN
SectionIn RO # Read-only, cannot be deselected
!insertmacro wails.setShellContext
# Check if this is an upgrade installation
${If} ${FileExists} "$INSTDIR\${PRODUCT_EXECUTABLE}"
- DetailPrint "正在升级: $INSTDIR"
+ DetailPrint "Upgrading: $INSTDIR"
# Backup important files before upgrade
Call BackupUserData
@@ -749,13 +749,13 @@ Section "主程序 (必需)" SEC_MAIN
Call StopRunningInstances
# Clean up old files but preserve user data
- DetailPrint "正在清理旧版本文件..."
+ DetailPrint "Cleaning up old version files..."
Delete "$INSTDIR\${PRODUCT_EXECUTABLE}"
Delete "$INSTDIR\BS2PRO-Core.exe"
RMDir /r "$INSTDIR\bridge"
Delete "$INSTDIR\logs\*.log" # Keep log structure but remove old logs
${ElseIf} ${FileExists} "$INSTDIR\BS2PRO-Core.exe"
- DetailPrint "正在升级 (发现Core): $INSTDIR"
+ DetailPrint "Upgrading (found Core): $INSTDIR"
# Backup important files before upgrade
Call BackupUserData
@@ -764,19 +764,19 @@ Section "主程序 (必需)" SEC_MAIN
Call StopRunningInstances
# Clean up old files but preserve user data
- DetailPrint "正在清理旧版本文件..."
+ DetailPrint "Cleaning up old version files..."
Delete "$INSTDIR\${PRODUCT_EXECUTABLE}"
Delete "$INSTDIR\BS2PRO-Core.exe"
RMDir /r "$INSTDIR\bridge"
Delete "$INSTDIR\logs\*.log"
${Else}
- DetailPrint "全新安装: $INSTDIR"
+ DetailPrint "Fresh installation: $INSTDIR"
# Ensure old instances are completely stopped before installing
Call StopRunningInstances
# Clean up any leftover files from previous installation
- DetailPrint "正在清理残留文件..."
+ DetailPrint "Cleaning up leftover files..."
RMDir /r "$INSTDIR\bridge"
Delete "$INSTDIR\logs\*.*"
${EndIf}
@@ -788,11 +788,11 @@ Section "主程序 (必需)" SEC_MAIN
!insertmacro wails.files
# Copy core service executable
- DetailPrint "正在安装核心服务..."
+ DetailPrint "Installing core service..."
File "..\..\bin\BS2PRO-Core.exe"
# Copy bridge directory and its contents
- DetailPrint "正在安装桥接组件..."
+ DetailPrint "Installing bridge components..."
SetOutPath $INSTDIR\bridge
File /r "..\..\bin\bridge\*.*"
@@ -803,7 +803,7 @@ Section "主程序 (必需)" SEC_MAIN
Call RestoreUserData
# Create shortcuts
- DetailPrint "正在创建快捷方式..."
+ DetailPrint "Creating shortcuts..."
CreateShortcut "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
CreateShortCut "$DESKTOP\${INFO_PRODUCTNAME}.lnk" "$INSTDIR\${PRODUCT_EXECUTABLE}"
@@ -813,23 +813,23 @@ Section "主程序 (必需)" SEC_MAIN
!insertmacro wails.writeUninstaller
Call WriteCurrentVersionInfo
- DetailPrint "安装完成"
+ DetailPrint "Installation completed"
# Show completion message
${If} ${FileExists} "$TEMP\bs2pro_config_backup.json"
- MessageBox MB_OK|MB_ICONINFORMATION "BS2PRO 控制器升级成功!$\n$\n您的设置已保留。$\n$\n注意:完整功能需要管理员权限。"
+ MessageBox MB_OK|MB_ICONINFORMATION "BS2PRO Controller upgraded successfully!$\n$\nYour settings have been preserved.$\n$\nNote: Full functionality requires administrator privileges."
Delete "$TEMP\bs2pro_config_backup.json" # Clean up backup
${Else}
- MessageBox MB_OK|MB_ICONINFORMATION "BS2PRO 控制器安装成功!$\n$\n注意:完整功能需要管理员权限。"
+ MessageBox MB_OK|MB_ICONINFORMATION "BS2PRO Controller installed successfully!$\n$\nNote: Full functionality requires administrator privileges."
${EndIf}
SectionEnd
# Auto-start section (selected by default)
-Section "开机自启动" SEC_AUTOSTART
- DetailPrint "正在配置开机自启动..."
+Section "Auto-start on boot" SEC_AUTOSTART
+ DetailPrint "Configuring auto-start on boot..."
# First, remove any existing auto-start entries to ensure clean state
- DetailPrint "正在清理现有自启动项..."
+ DetailPrint "Cleaning up existing auto-start entries..."
nsExec::ExecToStack '"$SYSDIR\schtasks.exe" /delete /tn "BS2PRO-Controller" /f'
Pop $0
Pop $1
@@ -842,7 +842,7 @@ Section "开机自启动" SEC_AUTOSTART
DeleteRegValue HKLM "Software\Microsoft\Windows\CurrentVersion\Run" "BS2PRO-Core"
# Create new scheduled task for auto-start with admin privileges
- DetailPrint "正在创建自启动计划任务..."
+ DetailPrint "Creating auto-start scheduled task..."
# Use schtasks to create a task that runs at logon with highest privileges
# The task will start BS2PRO-Core.exe with --autostart flag after 15 seconds delay
@@ -850,19 +850,19 @@ Section "开机自启动" SEC_AUTOSTART
Pop $0
Pop $1
${If} $0 == 0
- DetailPrint "开机自启动配置成功(计划任务)"
+ DetailPrint "Auto-start configured successfully (scheduled task)"
${Else}
- DetailPrint "计划任务创建失败,使用注册表方式..."
+ DetailPrint "Scheduled task creation failed, using registry method..."
# Fallback: use registry auto-start (will trigger UAC on each login)
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Run" "BS2PRO-Controller" '"$INSTDIR\BS2PRO-Core.exe" --autostart'
- DetailPrint "开机自启动配置成功(注册表)"
+ DetailPrint "Auto-start configured successfully (registry)"
${EndIf}
SectionEnd
# Required PawnIO installer section
-Section "安装 PawnIO (必需)" SEC_PAWNIO
+Section "Install PawnIO (Required)" SEC_PAWNIO
SectionIn RO
- DetailPrint "正在准备安装 PawnIO..."
+ DetailPrint "Preparing to install PawnIO..."
Push $6
Push $7
Push $8
@@ -871,7 +871,7 @@ Section "安装 PawnIO (必需)" SEC_PAWNIO
SetOutPath "$TEMP\BS2PRO-PawnIO"
File /nonfatal "..\..\bin\PawnIO_setup.exe"
${IfNot} ${FileExists} "$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe"
- MessageBox MB_OK|MB_ICONSTOP "未找到 PawnIO_setup.exe(build\\bin)。请先执行 build_bridge.bat 下载后再打包安装器。"
+ MessageBox MB_OK|MB_ICONSTOP "PawnIO_setup.exe not found (build\\bin). Please run build_bridge.bat to download it before building the installer."
Abort
${EndIf}
@@ -890,11 +890,11 @@ Section "安装 PawnIO (必需)" SEC_PAWNIO
StrCpy $9 "1"
${If} $6 != ""
- DetailPrint "检测到已安装 PawnIO (版本: $6),内置版本: ${PAWNIO_BUNDLED_VERSION}"
+ DetailPrint "Detected installed PawnIO (version: $6), bundled version: ${PAWNIO_BUNDLED_VERSION}"
${VersionCompare} "$6" "${PAWNIO_BUNDLED_VERSION}" $8
${If} $8 == 2
- MessageBox MB_YESNO|MB_ICONQUESTION "检测到 PawnIO 旧版本:$6。$\n安装包内置版本:${PAWNIO_BUNDLED_VERSION}。$\n$\n是否先卸载旧版本再安装新版本?" IDYES pawnio_upgrade IDNO pawnio_skip
+ MessageBox MB_YESNO|MB_ICONQUESTION "Detected older PawnIO version: $6.$\nBundled version: ${PAWNIO_BUNDLED_VERSION}.$\n$\nUninstall the old version and install the new one?" IDYES pawnio_upgrade IDNO pawnio_skip
pawnio_upgrade:
StrCpy $9 "2"
Goto pawnio_apply
@@ -902,7 +902,7 @@ Section "安装 PawnIO (必需)" SEC_PAWNIO
StrCpy $9 "0"
Goto pawnio_apply
${Else}
- MessageBox MB_YESNO|MB_ICONQUESTION "检测到 PawnIO 已安装(版本:$6)。$\n$\n是否执行 PawnIO 修复安装(卸载后重装)?" IDYES pawnio_repair IDNO pawnio_skip2
+ MessageBox MB_YESNO|MB_ICONQUESTION "PawnIO is already installed (version: $6).$\n$\nPerform PawnIO repair installation (uninstall then reinstall)?" IDYES pawnio_repair IDNO pawnio_skip2
pawnio_repair:
StrCpy $9 "2"
Goto pawnio_apply
@@ -914,7 +914,7 @@ Section "安装 PawnIO (必需)" SEC_PAWNIO
pawnio_apply:
${If} $9 == "0"
- DetailPrint "用户选择跳过 PawnIO 处理。"
+ DetailPrint "User chose to skip PawnIO processing."
Goto pawnio_done
${EndIf}
@@ -923,7 +923,7 @@ Section "安装 PawnIO (必需)" SEC_PAWNIO
${EndIf}
# Pre-clean possible stale driver service state (avoids driver install error 1072)
- DetailPrint "正在清理旧的 PawnIO 驱动服务..."
+ DetailPrint "Cleaning up old PawnIO driver services..."
nsExec::ExecToStack '"$SYSDIR\sc.exe" stop "PawnIO"'
Pop $4
Pop $5
@@ -938,31 +938,31 @@ Section "安装 PawnIO (必需)" SEC_PAWNIO
Pop $5
Sleep 1200
- DetailPrint "正在静默安装 PawnIO(最多等待 60 秒)..."
+ DetailPrint "Installing PawnIO silently (up to 60 seconds)..."
nsExec::ExecToStack /TIMEOUT=60000 '"$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe" -install -silent'
Pop $0
Pop $1
${If} $0 == "timeout"
- DetailPrint "PawnIO 静默安装 60 秒未响应,回退到交互安装..."
+ DetailPrint "PawnIO silent install timed out after 60 seconds, falling back to interactive install..."
nsExec::ExecToStack '"$SYSDIR\taskkill.exe" /F /IM "PawnIO_setup.exe" /T'
Pop $2
Pop $3
ExecWait '"$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe" -install' $0
${If} $0 == 0
- DetailPrint "PawnIO 安装完成(交互)"
+ DetailPrint "PawnIO installation completed (interactive)"
${Else}
- MessageBox MB_OK|MB_ICONSTOP "PawnIO 交互安装失败(返回码: $0)。$\n$\n常见原因:驱动服务被系统标记删除(错误 1072)。$\n请先重启系统后重新运行安装程序。"
+ MessageBox MB_OK|MB_ICONSTOP "PawnIO interactive install failed (return code: $0).$\n$\nCommon cause: driver service marked for deletion by system (error 1072).$\nPlease restart the system and run the installer again."
Abort
${EndIf}
${ElseIf} $0 == 0
- DetailPrint "PawnIO 安装完成(静默)"
+ DetailPrint "PawnIO installation completed (silent)"
${Else}
- DetailPrint "PawnIO 静默安装失败,改为交互安装..."
+ DetailPrint "PawnIO silent install failed, switching to interactive install..."
ExecWait '"$TEMP\BS2PRO-PawnIO\PawnIO_setup.exe" -install' $0
${If} $0 == 0
- DetailPrint "PawnIO 安装完成(交互)"
+ DetailPrint "PawnIO installation completed (interactive)"
${Else}
- MessageBox MB_OK|MB_ICONSTOP "PawnIO 安装失败(返回码: $0)。$\n$\n常见原因:驱动服务被系统标记删除(错误 1072)。$\n请先重启系统后重新运行安装程序。"
+ MessageBox MB_OK|MB_ICONSTOP "PawnIO installation failed (return code: $0).$\n$\nCommon cause: driver service marked for deletion by system (error 1072).$\nPlease restart the system and run the installer again."
Abort
${EndIf}
${EndIf}
@@ -976,19 +976,19 @@ SectionEnd
# Section descriptions
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
- !insertmacro MUI_DESCRIPTION_TEXT ${SEC_MAIN} "BS2PRO 控制器主程序和核心服务文件。"
- !insertmacro MUI_DESCRIPTION_TEXT ${SEC_AUTOSTART} "系统启动时自动运行 BS2PRO 控制器核心服务。推荐开启。"
- !insertmacro MUI_DESCRIPTION_TEXT ${SEC_PAWNIO} "安装 PawnIO 驱动,PawnIO将用于获取硬件相关信息。"
+ !insertmacro MUI_DESCRIPTION_TEXT ${SEC_MAIN} "BS2PRO Controller main program and core service files."
+ !insertmacro MUI_DESCRIPTION_TEXT ${SEC_AUTOSTART} "Automatically run BS2PRO Controller core service at system startup. Recommended."
+ !insertmacro MUI_DESCRIPTION_TEXT ${SEC_PAWNIO} "Install PawnIO driver, used for obtaining hardware information."
!insertmacro MUI_FUNCTION_DESCRIPTION_END
Section "uninstall"
!insertmacro wails.setShellContext
# Stop running instances before uninstalling
- DetailPrint "正在停止运行中的进程..."
+ DetailPrint "Stopping running processes..."
# Stop core service first (ignore errors)
- DetailPrint "正在停止 BS2PRO-Core.exe..."
+ DetailPrint "Stopping BS2PRO-Core.exe..."
nsExec::ExecToStack '"$SYSDIR\taskkill.exe" /IM "BS2PRO-Core.exe" /T'
Pop $0
Pop $1
@@ -998,7 +998,7 @@ Section "uninstall"
Pop $1
# Stop main application (ignore errors)
- DetailPrint "正在停止 ${PRODUCT_EXECUTABLE}..."
+ DetailPrint "Stopping ${PRODUCT_EXECUTABLE}..."
nsExec::ExecToStack '"$SYSDIR\taskkill.exe" /IM "${PRODUCT_EXECUTABLE}" /T'
Pop $0
Pop $1
@@ -1019,7 +1019,7 @@ Section "uninstall"
Pop $1
# Stop bridge processes (ignore errors)
- DetailPrint "正在停止 TempBridge.exe..."
+ DetailPrint "Stopping TempBridge.exe..."
nsExec::ExecToStack '"$SYSDIR\taskkill.exe" /IM "TempBridge.exe" /T'
Pop $0
Pop $1
@@ -1032,7 +1032,7 @@ Section "uninstall"
Call un.StopBridgeDriver
# Remove auto-start entries
- DetailPrint "正在移除自启动项..."
+ DetailPrint "Removing auto-start entries..."
# Remove scheduled task (ignore errors if not exists)
nsExec::ExecToStack '"$SYSDIR\schtasks.exe" /delete /tn "BS2PRO-Controller" /f'
@@ -1056,29 +1056,29 @@ Section "uninstall"
Sleep 2000
# Remove application data directories
- DetailPrint "正在移除应用数据..."
+ DetailPrint "Removing application data..."
RMDir /r "$AppData\${PRODUCT_EXECUTABLE}" # Remove the WebView2 DataPath
RMDir /r "$APPDATA\BS2PRO-Controller"
RMDir /r "$LOCALAPPDATA\BS2PRO-Controller"
RMDir /r "$TEMP\BS2PRO-Controller"
# Remove installation directory and all contents
- DetailPrint "正在移除安装文件..."
+ DetailPrint "Removing installation files..."
# Remove bridge directory (contains TempBridge.exe and related files)
- DetailPrint "正在删除桥接组件..."
+ DetailPrint "Deleting bridge components..."
RMDir /r "$INSTDIR\bridge"
# Remove logs directory
- DetailPrint "正在删除日志文件..."
+ DetailPrint "Deleting log files..."
RMDir /r "$INSTDIR\logs"
# Remove entire installation directory
- DetailPrint "正在删除安装目录..."
+ DetailPrint "Deleting installation directory..."
RMDir /r $INSTDIR
# Remove shortcuts
- DetailPrint "正在移除快捷方式..."
+ DetailPrint "Removing shortcuts..."
Delete "$SMPROGRAMS\${INFO_PRODUCTNAME}.lnk"
Delete "$DESKTOP\${INFO_PRODUCTNAME}.lnk"
@@ -1087,10 +1087,10 @@ Section "uninstall"
!insertmacro wails.deleteUninstaller
- DetailPrint "卸载完成"
+ DetailPrint "Uninstallation completed"
# Optional: Ask user if they want to remove configuration files
- MessageBox MB_YESNO|MB_ICONQUESTION "是否删除所有配置文件和日志?" IDNO skip_config
+ MessageBox MB_YESNO|MB_ICONQUESTION "Delete all configuration files and logs?" IDNO skip_config
RMDir /r "$APPDATA\BS2PRO"
RMDir /r "$LOCALAPPDATA\BS2PRO"
skip_config:
diff --git a/build_bridge.bat b/build_bridge.bat
index ca417cd..91fec91 100644
--- a/build_bridge.bat
+++ b/build_bridge.bat
@@ -1,5 +1,5 @@
@echo off
-echo ڹ¶Žӳ...
+echo Building temperature bridge program...
set "ROOT=%~dp0"
set "PROJECT=%ROOT%bridge\TempBridge\TempBridge.csproj"
@@ -11,26 +11,26 @@ set "PAWNIO_OUT=%BUILDROOT%\PawnIO_setup.exe"
if not exist "%OUTDIR%" mkdir "%OUTDIR%"
if not exist "%BUILDROOT%" mkdir "%BUILDROOT%"
-echo ԭNuGet...
+echo Restoring NuGet packages...
dotnet restore "%PROJECT%"
if errorlevel 1 goto :error
-echo 뷢汾...
+echo Building release version...
dotnet publish "%PROJECT%" -c Release --self-contained false -o "%OUTDIR%"
if errorlevel 1 goto :error
-echo PawnIO װ...
+echo Downloading PawnIO installer...
powershell -NoProfile -ExecutionPolicy Bypass -Command "try { [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; Invoke-WebRequest -Uri '%PAWNIO_URL%' -OutFile '%PAWNIO_OUT%' -UseBasicParsing; exit 0 } catch { Write-Error $_; exit 1 }"
if errorlevel 1 goto :error
if not exist "%PAWNIO_OUT%" goto :error
-echo PawnIO : %PAWNIO_OUT%
+echo PawnIO downloaded to: %PAWNIO_OUT%
-echo ɣĿ¼: %OUTDIR%
+echo Build completed, output directory: %OUTDIR%
goto :end
:error
-echo ʧܣ鿴Ϸ־
+echo Build failed! Please check the logs above.
:end
pause
diff --git a/cmd/core/app.go b/cmd/core/app.go
index 7a79617..d224796 100644
--- a/cmd/core/app.go
+++ b/cmd/core/app.go
@@ -32,11 +32,11 @@ import (
//go:embed icon.ico
var iconData []byte
-// CoreApp 核心应用结构
+// CoreApp is the core application structure
type CoreApp struct {
ctx context.Context
- // 管理器
+ // Managers
deviceManager *device.Manager
bridgeManager *bridge.Manager
tempReader *temperature.Reader
@@ -48,7 +48,7 @@ type CoreApp struct {
logger *logger.CustomLogger
ipcServer *ipc.Server
- // 状态
+ // State
isConnected bool
monitoringTemp bool
currentTemp types.TemperatureData
@@ -57,36 +57,36 @@ type CoreApp struct {
isAutoStartLaunch bool
debugMode bool
- // 监控相关
+ // Monitoring related
guiLastResponse int64
guiMonitorEnabled bool
healthCheckTicker *time.Ticker
cleanupChan chan bool
quitChan chan bool
- // 同步
+ // Synchronization
mutex sync.RWMutex
stopMonitoring chan bool
manualGearLevelMemory map[string]string
}
-// NewCoreApp 创建核心应用实例
+// NewCoreApp creates a new core application instance
func NewCoreApp(debugMode, isAutoStart bool) *CoreApp {
- // 初始化日志系统
+ // Initialize logging system
installDir := config.GetInstallDir()
customLogger, err := logger.NewCustomLogger(debugMode, installDir)
if err != nil {
- // 如果初始化失败,无法记录,直接退出
- panic(fmt.Sprintf("初始化日志系统失败: %v", err))
+ // If initialization fails, cannot log, exit directly
+ panic(fmt.Sprintf("Failed to initialize logging system: %v", err))
} else {
- customLogger.Info("核心服务启动")
- customLogger.Info("安装目录: %s", installDir)
- customLogger.Info("调试模式: %v", debugMode)
- customLogger.Info("自启动模式: %v", isAutoStart)
+ customLogger.Info("Core service started")
+ customLogger.Info("Install directory: %s", installDir)
+ customLogger.Info("Debug mode: %v", debugMode)
+ customLogger.Info("Auto-start mode: %v", isAutoStart)
customLogger.CleanOldLogs()
}
- // 创建管理器
+ // Create managers
bridgeMgr := bridge.NewManager(customLogger)
deviceMgr := device.NewManager(customLogger)
tempReader := temperature.NewReader(bridgeMgr, customLogger)
@@ -116,10 +116,10 @@ func NewCoreApp(debugMode, isAutoStart bool) *CoreApp {
quitChan: make(chan bool, 1),
guiMonitorEnabled: true,
manualGearLevelMemory: map[string]string{
- "静音": "中",
- "标准": "中",
- "强劲": "中",
- "超频": "中",
+ "Silent": "Mid",
+ "Standard": "Mid",
+ "Performance": "Mid",
+ "Overclock": "Mid",
},
}
app.notifier = notifier.NewManager(customLogger, iconData)
@@ -128,122 +128,122 @@ func NewCoreApp(debugMode, isAutoStart bool) *CoreApp {
return app
}
-// Start 启动核心服务
+// Start starts the core service
func (a *CoreApp) Start() error {
- a.logInfo("=== BS2PRO 核心服务启动 ===")
- a.logInfo("版本: %s", version.Get())
- a.logInfo("安装目录: %s", config.GetInstallDir())
- a.logInfo("调试模式: %v", a.debugMode)
- a.logInfo("当前工作目录: %s", config.GetCurrentWorkingDir())
+ a.logInfo("=== BS2PRO Core Service Starting ===")
+ a.logInfo("Version: %s", version.Get())
+ a.logInfo("Install directory: %s", config.GetInstallDir())
+ a.logInfo("Debug mode: %v", a.debugMode)
+ a.logInfo("Current working directory: %s", config.GetCurrentWorkingDir())
- // 检测是否为自启动
+ // Detect if this is an auto-start launch
a.isAutoStartLaunch = autostart.DetectAutoStartLaunch(os.Args)
- a.logInfo("自启动模式: %v", a.isAutoStartLaunch)
+ a.logInfo("Auto-start mode: %v", a.isAutoStartLaunch)
- // 加载配置
- a.logInfo("开始加载配置文件")
+ // Load configuration
+ a.logInfo("Loading configuration file")
cfg := a.configManager.Load(a.isAutoStartLaunch)
if normalizedLight, changed := normalizeLightStripConfig(cfg.LightStrip); changed {
cfg.LightStrip = normalizedLight
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存灯带默认配置失败: %v", err)
+ a.logError("Failed to save default light strip config: %v", err)
}
}
if normalizedSmart, changed := smartcontrol.NormalizeConfig(cfg.SmartControl, cfg.FanCurve, cfg.DebugMode); changed {
cfg.SmartControl = normalizedSmart
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存智能控温默认配置失败: %v", err)
+ a.logError("Failed to save default smart control config: %v", err)
}
}
if normalizeHotkeyConfig(&cfg) {
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存快捷键默认配置失败: %v", err)
+ a.logError("Failed to save default hotkey config: %v", err)
}
}
if normalizeCurveProfilesConfig(&cfg) {
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存温控曲线方案默认配置失败: %v", err)
+ a.logError("Failed to save default fan curve profile config: %v", err)
}
}
if normalizeManualGearMemoryConfig(&cfg) {
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存挡位记忆默认配置失败: %v", err)
+ a.logError("Failed to save default gear memory config: %v", err)
}
}
a.syncManualGearLevelMemory(cfg)
- a.logInfo("配置加载完成,配置路径: %s", cfg.ConfigPath)
+ a.logInfo("Configuration loaded, config path: %s", cfg.ConfigPath)
- // 同步调试模式配置
+ // Sync debug mode from config
if cfg.DebugMode {
a.debugMode = true
if a.logger != nil {
a.logger.SetDebugMode(true)
}
- a.logInfo("从配置文件同步调试模式: 启用")
+ a.logInfo("Synced debug mode from config: enabled")
}
- // 检查并同步Windows自启动状态
- a.logInfo("检查Windows自启动状态")
+ // Check and sync Windows auto-start status
+ a.logInfo("Checking Windows auto-start status")
actualAutoStart := a.autostartManager.CheckWindowsAutoStart()
if actualAutoStart != cfg.WindowsAutoStart {
cfg.WindowsAutoStart = actualAutoStart
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("同步Windows自启动状态时保存配置失败: %v", err)
+ a.logError("Failed to save config when syncing Windows auto-start status: %v", err)
} else {
- a.logInfo("已同步Windows自启动状态: %v", actualAutoStart)
+ a.logInfo("Synced Windows auto-start status: %v", actualAutoStart)
}
}
- // 初始化HID
- a.logInfo("初始化HID库")
+ // Initialize HID
+ a.logInfo("Initializing HID library")
if err := a.deviceManager.Init(); err != nil {
- a.logError("初始化HID库失败: %v", err)
+ a.logError("Failed to initialize HID library: %v", err)
return err
}
- a.logInfo("HID库初始化成功")
+ a.logInfo("HID library initialized successfully")
- // 设置设备回调
+ // Set device callbacks
a.deviceManager.SetCallbacks(a.onFanDataUpdate, a.onDeviceDisconnect)
- // 启动 IPC 服务器
- a.logInfo("启动 IPC 服务器")
+ // Start IPC server
+ a.logInfo("Starting IPC server")
a.ipcServer = ipc.NewServer(a.handleIPCRequest, a.logger)
if err := a.ipcServer.Start(); err != nil {
- a.logError("启动 IPC 服务器失败: %v", err)
+ a.logError("Failed to start IPC server: %v", err)
return err
}
- // 初始化系统托盘
- a.logInfo("开始初始化系统托盘")
+ // Initialize system tray
+ a.logInfo("Initializing system tray")
a.initSystemTray()
a.applyHotkeyBindings(cfg)
- // 启动健康监控
+ // Start health monitoring
if cfg.GuiMonitoring {
- a.logInfo("启动健康监控")
+ a.logInfo("Starting health monitoring")
a.safeGo("startHealthMonitoring", func() {
a.startHealthMonitoring()
})
}
- a.logInfo("=== BS2PRO 核心服务启动完成 ===")
+ a.logInfo("=== BS2PRO Core Service Started ===")
- // 软件启动后立即开始温度监控(与智能控温开关解耦)
+ // Start temperature monitoring immediately after launch (decoupled from smart control toggle)
a.safeGo("startTemperatureMonitoring@Start", func() {
a.startTemperatureMonitoring()
})
- // 尝试连接设备
+ // Try to connect device
a.safeGo("delayedConnectDevice", func() {
if a.isAutoStartLaunch {
- // 自启动时等待更长时间,让设备固件有足够时间完成初始化
- a.logInfo("自启动模式:等待设备初始化(3秒)")
+ // Wait longer during auto-start to allow device firmware to finish initialization
+ a.logInfo("Auto-start mode: waiting for device initialization (3 seconds)")
time.Sleep(3 * time.Second)
} else {
time.Sleep(1 * time.Second)
@@ -254,35 +254,35 @@ func (a *CoreApp) Start() error {
return nil
}
-// Stop 停止核心服务
+// Stop stops the core service
func (a *CoreApp) Stop() {
- a.logInfo("核心服务正在停止...")
+ a.logInfo("Core service stopping...")
a.stopTemperatureMonitoring()
if a.hotkeyManager != nil {
a.hotkeyManager.Stop()
}
- // 清理资源
+ // Clean up resources
a.cleanup()
- // 停止所有监控
+ // Stop all monitoring
a.DisconnectDevice()
- // 停止桥接程序
+ // Stop bridge program
a.bridgeManager.Stop()
- // 停止 IPC 服务器
+ // Stop IPC server
if a.ipcServer != nil {
a.ipcServer.Stop()
}
- // 停止托盘
+ // Stop tray
a.trayManager.Quit()
- a.logInfo("核心服务已停止")
+ a.logInfo("Core service stopped")
}
-// initSystemTray 初始化系统托盘
+// initSystemTray initializes the system tray
func (a *CoreApp) initSystemTray() {
a.trayManager.SetCallbacks(
a.onShowWindowRequest,
@@ -296,7 +296,7 @@ func (a *CoreApp) initSystemTray() {
func(profileID string) string {
profile, err := a.SetActiveFanCurveProfile(profileID)
if err != nil {
- a.logError("托盘设置温控曲线失败: %v", err)
+ a.logError("Tray failed to set fan curve profile: %v", err)
return ""
}
return profile.Name
@@ -310,7 +310,7 @@ func (a *CoreApp) initSystemTray() {
}
name := p.Name
if strings.TrimSpace(name) == "" {
- name = "默认"
+ name = "Default"
}
options = append(options, tray.CurveOption{ID: p.ID, Name: name})
}
@@ -332,7 +332,7 @@ func (a *CoreApp) initSystemTray() {
}
name := p.Name
if strings.TrimSpace(name) == "" {
- name = "默认"
+ name = "Default"
}
curveOptions = append(curveOptions, tray.CurveOption{ID: p.ID, Name: name})
}
@@ -351,42 +351,42 @@ func (a *CoreApp) initSystemTray() {
a.trayManager.Init()
}
-// onShowWindowRequest 显示窗口请求回调
+// onShowWindowRequest handles show window request callback
func (a *CoreApp) onShowWindowRequest() {
- a.logInfo("收到显示窗口请求")
+ a.logInfo("Received show window request")
- // 通知所有已连接的 GUI 客户端显示窗口
+ // Notify all connected GUI clients to show window
if a.ipcServer != nil && a.ipcServer.HasClients() {
a.ipcServer.BroadcastEvent("show-window", nil)
} else {
- // 没有 GUI 连接,启动 GUI
- a.logInfo("没有 GUI 连接,尝试启动 GUI")
+ // No GUI connection, launch GUI
+ a.logInfo("No GUI connection, attempting to launch GUI")
if err := launchGUI(); err != nil {
- a.logError("启动 GUI 失败: %v", err)
+ a.logError("Failed to launch GUI: %v", err)
}
}
}
-// onQuitRequest 退出请求回调
+// onQuitRequest handles quit request callback
func (a *CoreApp) onQuitRequest() {
- a.logInfo("收到退出请求")
+ a.logInfo("Received quit request")
- // 通知所有 GUI 客户端退出
+ // Notify all GUI clients to quit
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent("quit", nil)
}
- // 发送退出信号
+ // Send quit signal
select {
case a.quitChan <- true:
default:
}
}
-// handleIPCRequest 处理 IPC 请求
+// handleIPCRequest handles IPC requests
func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
switch req.Type {
- // 设备相关
+ // Device related
case ipc.ReqConnect:
success := a.ConnectDevice()
return a.successResponse(success)
@@ -403,7 +403,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
data := a.deviceManager.GetCurrentFanData()
return a.dataResponse(data)
- // 配置相关
+ // Config related
case ipc.ReqGetConfig:
cfg := a.configManager.Get()
return a.dataResponse(cfg)
@@ -411,7 +411,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqUpdateConfig:
var cfg types.AppConfig
if err := json.Unmarshal(req.Data, &cfg); err != nil {
- return a.errorResponse("解析配置失败: " + err.Error())
+ return a.errorResponse("Failed to parse config: " + err.Error())
}
if err := a.UpdateConfig(cfg); err != nil {
return a.errorResponse(err.Error())
@@ -421,7 +421,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetFanCurve:
var curve []types.FanCurvePoint
if err := json.Unmarshal(req.Data, &curve); err != nil {
- return a.errorResponse("解析风扇曲线失败: " + err.Error())
+ return a.errorResponse("Failed to parse fan curve: " + err.Error())
}
if err := a.SetFanCurve(curve); err != nil {
return a.errorResponse(err.Error())
@@ -438,7 +438,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetActiveFanCurveProfile:
var params ipc.SetActiveFanCurveProfileParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
profile, err := a.SetActiveFanCurveProfile(params.ID)
if err != nil {
@@ -449,7 +449,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSaveFanCurveProfile:
var params ipc.SaveFanCurveProfileParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
profile, err := a.SaveFanCurveProfile(params)
if err != nil {
@@ -460,7 +460,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqDeleteFanCurveProfile:
var params ipc.DeleteFanCurveProfileParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.DeleteFanCurveProfile(params.ID); err != nil {
return a.errorResponse(err.Error())
@@ -477,18 +477,18 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqImportFanCurveProfiles:
var params ipc.ImportFanCurveProfilesParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.ImportFanCurveProfiles(params.Code); err != nil {
return a.errorResponse(err.Error())
}
return a.successResponse(true)
- // 控制相关
+ // Control related
case ipc.ReqSetAutoControl:
var params ipc.SetAutoControlParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.SetAutoControl(params.Enabled); err != nil {
return a.errorResponse(err.Error())
@@ -498,7 +498,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetManualGear:
var params ipc.SetManualGearParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
success := a.SetManualGear(params.Gear, params.Level)
return a.successResponse(success)
@@ -510,7 +510,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetCustomSpeed:
var params ipc.SetCustomSpeedParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.SetCustomSpeed(params.Enabled, params.RPM); err != nil {
return a.errorResponse(err.Error())
@@ -520,7 +520,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetGearLight:
var params ipc.SetBoolParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
success := a.SetGearLight(params.Enabled)
return a.successResponse(success)
@@ -528,7 +528,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetPowerOnStart:
var params ipc.SetBoolParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
success := a.SetPowerOnStart(params.Enabled)
return a.successResponse(success)
@@ -536,7 +536,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetSmartStartStop:
var params ipc.SetStringParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
success := a.SetSmartStartStop(params.Value)
return a.successResponse(success)
@@ -544,7 +544,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetBrightness:
var params ipc.SetIntParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
success := a.SetBrightness(params.Value)
return a.successResponse(success)
@@ -552,14 +552,14 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetLightStrip:
var params ipc.SetLightStripParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.SetLightStrip(params.Config); err != nil {
return a.errorResponse(err.Error())
}
return a.successResponse(true)
- // 温度相关
+ // Temperature related
case ipc.ReqGetTemperature:
a.mutex.RLock()
temp := a.currentTemp
@@ -578,11 +578,11 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
status := a.bridgeManager.GetStatus()
return a.dataResponse(status)
- // 自启动相关
+ // Auto-start related
case ipc.ReqSetWindowsAutoStart:
var params ipc.SetBoolParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.SetWindowsAutoStart(params.Enabled); err != nil {
return a.errorResponse(err.Error())
@@ -604,20 +604,20 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetAutoStartWithMethod:
var params ipc.SetAutoStartWithMethodParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.autostartManager.SetAutoStartWithMethod(params.Enable, params.Method); err != nil {
return a.errorResponse(err.Error())
}
return a.successResponse(true)
- // 窗口相关
+ // Window related
case ipc.ReqShowWindow:
a.onShowWindowRequest()
return a.successResponse(true)
case ipc.ReqHideWindow:
- // GUI 自己处理隐藏
+ // GUI handles hiding itself
return a.successResponse(true)
case ipc.ReqQuitApp:
@@ -626,7 +626,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
})
return a.successResponse(true)
- // 调试相关
+ // Debug related
case ipc.ReqGetDebugInfo:
info := a.GetDebugInfo()
return a.dataResponse(info)
@@ -634,7 +634,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
case ipc.ReqSetDebugMode:
var params ipc.SetBoolParams
if err := json.Unmarshal(req.Data, ¶ms); err != nil {
- return a.errorResponse("解析参数失败: " + err.Error())
+ return a.errorResponse("Failed to parse parameters: " + err.Error())
}
if err := a.SetDebugMode(params.Enabled); err != nil {
return a.errorResponse(err.Error())
@@ -645,7 +645,7 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
atomic.StoreInt64(&a.guiLastResponse, time.Now().Unix())
return a.successResponse(true)
- // 系统相关
+ // System related
case ipc.ReqPing:
return a.dataResponse("pong")
@@ -653,11 +653,11 @@ func (a *CoreApp) handleIPCRequest(req ipc.Request) ipc.Response {
return a.dataResponse(a.isAutoStartLaunch)
default:
- return a.errorResponse(fmt.Sprintf("未知的请求类型: %s", req.Type))
+ return a.errorResponse(fmt.Sprintf("Unknown request type: %s", req.Type))
}
}
-// 响应辅助方法
+// Response helper methods
func (a *CoreApp) successResponse(success bool) ipc.Response {
data, _ := json.Marshal(success)
return ipc.Response{Success: true, Data: data}
@@ -670,31 +670,31 @@ func (a *CoreApp) errorResponse(errMsg string) ipc.Response {
func (a *CoreApp) dataResponse(data any) ipc.Response {
dataBytes, err := json.Marshal(data)
if err != nil {
- return a.errorResponse("序列化数据失败: " + err.Error())
+ return a.errorResponse("Failed to serialize data: " + err.Error())
}
return ipc.Response{Success: true, Data: dataBytes}
}
-// onFanDataUpdate 风扇数据更新回调
+// onFanDataUpdate handles fan data update callback
func (a *CoreApp) onFanDataUpdate(fanData *types.FanData) {
a.mutex.Lock()
cfg := a.configManager.Get()
- // 检查工作模式变化
- // 如果开启了"断连保持配置模式",则忽略设备状态变化,避免误判
+ // Check work mode changes
+ // If "keep config on disconnect" mode is enabled, ignore device state changes to avoid false detection
if fanData.WorkMode == "挡位工作模式" &&
cfg.AutoControl &&
a.lastDeviceMode == "自动模式(实时转速)" &&
!a.userSetAutoControl &&
!cfg.IgnoreDeviceOnReconnect {
- a.logInfo("检测到设备从自动模式切换到挡位工作模式,自动关闭智能变频")
+ a.logInfo("Detected device switched from auto mode to gear mode, automatically disabling smart fan control")
cfg.AutoControl = false
a.configManager.Set(cfg)
a.configManager.Save()
- // 广播配置更新
+ // Broadcast config update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventConfigUpdate, cfg)
}
@@ -703,7 +703,7 @@ func (a *CoreApp) onFanDataUpdate(fanData *types.FanData) {
a.lastDeviceMode == "自动模式(实时转速)" &&
!a.userSetAutoControl &&
cfg.IgnoreDeviceOnReconnect {
- a.logInfo("检测到设备模式变化,但已开启断连保持配置模式,保持APP配置不变")
+ a.logInfo("Device mode change detected, but keep-config-on-disconnect mode is enabled, keeping app config unchanged")
}
a.lastDeviceMode = fanData.WorkMode
@@ -714,13 +714,13 @@ func (a *CoreApp) onFanDataUpdate(fanData *types.FanData) {
a.mutex.Unlock()
- // 广播风扇数据更新
+ // Broadcast fan data update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventFanDataUpdate, fanData)
}
}
-// onDeviceDisconnect 设备断开回调
+// onDeviceDisconnect handles device disconnect callback
func (a *CoreApp) onDeviceDisconnect() {
a.mutex.Lock()
wasConnected := a.isConnected
@@ -728,22 +728,22 @@ func (a *CoreApp) onDeviceDisconnect() {
a.mutex.Unlock()
if wasConnected {
- a.logInfo("设备连接已断开,将在健康检查时尝试自动重连")
+ a.logInfo("Device disconnected, will attempt auto-reconnect during health check")
}
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventDeviceDisconnected, nil)
}
- // 启动自动重连机制
+ // Start auto-reconnect mechanism
a.safeGo("scheduleReconnect", func() {
a.scheduleReconnect()
})
}
-// scheduleReconnect 安排设备重连
+// scheduleReconnect schedules device reconnection
func (a *CoreApp) scheduleReconnect() {
- // 延迟一段时间后尝试重连,避免频繁重试
+ // Delay before reconnect attempts to avoid frequent retries
retryDelays := []time.Duration{
2 * time.Second,
5 * time.Second,
@@ -752,49 +752,49 @@ func (a *CoreApp) scheduleReconnect() {
}
for i, delay := range retryDelays {
- // 检查是否已经连接(可能其他途径已重连)
+ // Check if already connected (may have reconnected via other means)
a.mutex.RLock()
connected := a.isConnected
a.mutex.RUnlock()
if connected {
- a.logInfo("设备已重新连接,停止重连尝试")
+ a.logInfo("Device reconnected, stopping reconnect attempts")
return
}
- a.logInfo("等待 %v 后尝试第 %d 次重连...", delay, i+1)
+ a.logInfo("Waiting %v before reconnect attempt #%d...", delay, i+1)
time.Sleep(delay)
- // 再次检查连接状态
+ // Check connection status again
a.mutex.RLock()
connected = a.isConnected
a.mutex.RUnlock()
if connected {
- a.logInfo("设备已重新连接,停止重连尝试")
+ a.logInfo("Device reconnected, stopping reconnect attempts")
return
}
- a.logInfo("尝试第 %d 次重连设备...", i+1)
+ a.logInfo("Attempting device reconnect #%d...", i+1)
if a.ConnectDevice() {
- a.logInfo("设备重连成功")
+ a.logInfo("Device reconnected successfully")
- // 如果开启了断连保持配置模式,重新应用APP配置
+ // If keep-config-on-disconnect mode is enabled, reapply app config
cfg := a.configManager.Get()
if cfg.IgnoreDeviceOnReconnect {
- a.logInfo("断连保持配置模式已开启,重新应用APP配置")
+ a.logInfo("Keep-config-on-disconnect mode enabled, reapplying app config")
a.reapplyConfigAfterReconnect()
}
return
}
- a.logError("第 %d 次重连失败", i+1)
+ a.logError("Reconnect attempt #%d failed", i+1)
}
- a.logError("所有重连尝试均失败,等待下次健康检查")
+ a.logError("All reconnect attempts failed, waiting for next health check")
}
-// ConnectDevice 连接设备
+// ConnectDevice connects to the device
func (a *CoreApp) ConnectDevice() bool {
success, deviceInfo := a.deviceManager.Connect()
if success {
@@ -807,18 +807,18 @@ func (a *CoreApp) ConnectDevice() bool {
}
if err := a.applyConfiguredLightStrip(); err != nil {
- a.logError("应用灯带配置失败: %v", err)
+ a.logError("Failed to apply light strip config: %v", err)
}
a.safeGo("startTemperatureMonitoring@ConnectDevice", func() {
a.startTemperatureMonitoring()
})
} else if a.ipcServer != nil {
- a.ipcServer.BroadcastEvent(ipc.EventDeviceError, "连接失败")
+ a.ipcServer.BroadcastEvent(ipc.EventDeviceError, "Connection failed")
}
return success
}
-// DisconnectDevice 断开设备连接
+// DisconnectDevice disconnects from the device
func (a *CoreApp) DisconnectDevice() {
a.mutex.Lock()
a.isConnected = false
@@ -831,43 +831,43 @@ func (a *CoreApp) DisconnectDevice() {
}
}
-// reapplyConfigAfterReconnect 重连后重新应用APP配置
+// reapplyConfigAfterReconnect reapplies app config after device reconnection
func (a *CoreApp) reapplyConfigAfterReconnect() {
cfg := a.configManager.Get()
- // 重新应用智能变频配置
+ // Reapply smart fan control config
if cfg.AutoControl {
- a.logInfo("重新启动智能变频")
+ a.logInfo("Restarting smart fan control")
} else if cfg.CustomSpeedEnabled {
- // 重新应用自定义转速
- a.logInfo("重新应用自定义转速: %d RPM", cfg.CustomSpeedRPM)
+ // Reapply custom speed
+ a.logInfo("Reapplying custom speed: %d RPM", cfg.CustomSpeedRPM)
if !a.deviceManager.SetCustomFanSpeed(cfg.CustomSpeedRPM) {
- a.logError("重新应用自定义转速失败")
+ a.logError("Failed to reapply custom speed")
}
}
- // 重新应用挡位灯配置
+ // Reapply gear light config
if cfg.GearLight {
- a.logInfo("重新开启挡位灯")
+ a.logInfo("Re-enabling gear light")
if !a.deviceManager.SetGearLight(true) {
- a.logError("重新开启挡位灯失败")
+ a.logError("Failed to re-enable gear light")
}
}
- // 重新应用通电自启动配置
+ // Reapply power-on auto-start config
if cfg.PowerOnStart {
- a.logInfo("重新开启通电自启动")
+ a.logInfo("Re-enabling power-on auto-start")
if !a.deviceManager.SetPowerOnStart(true) {
- a.logError("重新开启通电自启动失败")
+ a.logError("Failed to re-enable power-on auto-start")
}
}
if err := a.applyConfiguredLightStrip(); err != nil {
- a.logError("重连后重新应用灯带配置失败: %v", err)
+ a.logError("Failed to reapply light strip config after reconnect: %v", err)
}
}
-// GetDeviceStatus 获取设备状态
+// GetDeviceStatus gets device status
func (a *CoreApp) GetDeviceStatus() map[string]any {
a.mutex.RLock()
defer a.mutex.RUnlock()
@@ -896,7 +896,7 @@ func (a *CoreApp) GetDeviceStatus() map[string]any {
}
}
-// UpdateConfig 更新配置
+// UpdateConfig updates the configuration
func (a *CoreApp) UpdateConfig(cfg types.AppConfig) error {
a.mutex.Lock()
defer a.mutex.Unlock()
@@ -928,7 +928,7 @@ func (a *CoreApp) UpdateConfig(cfg types.AppConfig) error {
return nil
}
-// SetFanCurve 设置风扇曲线
+// SetFanCurve sets the fan curve
func (a *CoreApp) SetFanCurve(curve []types.FanCurvePoint) error {
a.mutex.Lock()
defer a.mutex.Unlock()
@@ -944,7 +944,7 @@ func (a *CoreApp) SetFanCurve(curve []types.FanCurvePoint) error {
return a.configManager.Update(cfg)
}
-// SetAutoControl 设置智能变频
+// SetAutoControl sets smart fan control
func (a *CoreApp) SetAutoControl(enabled bool) error {
a.mutex.Lock()
defer a.mutex.Unlock()
@@ -952,7 +952,7 @@ func (a *CoreApp) SetAutoControl(enabled bool) error {
cfg := a.configManager.Get()
if enabled && cfg.CustomSpeedEnabled {
- return fmt.Errorf("自定义转速模式下无法开启智能变频")
+ return fmt.Errorf("cannot enable smart fan control while custom speed mode is active")
}
cfg.AutoControl = enabled
@@ -977,7 +977,7 @@ func (a *CoreApp) SetAutoControl(enabled bool) error {
return err
}
-// applyCurrentGearSetting 应用当前挡位设置
+// applyCurrentGearSetting applies the current gear setting
func (a *CoreApp) applyCurrentGearSetting() {
fanData := a.deviceManager.GetCurrentFanData()
if fanData == nil {
@@ -991,11 +991,11 @@ func (a *CoreApp) applyCurrentGearSetting() {
}
level := a.getRememberedManualLevel(setGear, cfg.ManualLevel)
- a.logInfo("应用当前挡位设置: %s %s", setGear, level)
+ a.logInfo("Applying current gear setting: %s %s", setGear, level)
a.deviceManager.SetManualGear(setGear, level)
}
-// SetManualGear 设置手动挡位
+// SetManualGear sets the manual gear
func (a *CoreApp) SetManualGear(gear, level string) bool {
cfg := a.configManager.Get()
cfg.ManualGear = gear
@@ -1014,7 +1014,7 @@ func (a *CoreApp) SetManualGear(gear, level string) bool {
return a.deviceManager.SetManualGear(gear, level)
}
-// SetCustomSpeed 设置自定义转速
+// SetCustomSpeed sets the custom fan speed
func (a *CoreApp) SetCustomSpeed(enabled bool, rpm int) error {
a.mutex.Lock()
defer a.mutex.Unlock()
@@ -1048,7 +1048,7 @@ func (a *CoreApp) SetCustomSpeed(enabled bool, rpm int) error {
return err
}
-// SetGearLight 设置挡位灯
+// SetGearLight sets the gear indicator light
func (a *CoreApp) SetGearLight(enabled bool) bool {
if !a.deviceManager.SetGearLight(enabled) {
return false
@@ -1058,14 +1058,14 @@ func (a *CoreApp) SetGearLight(enabled bool) bool {
cfg.GearLight = enabled
a.configManager.Update(cfg)
- // 广播配置更新
+ // Broadcast config update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventConfigUpdate, cfg)
}
return true
}
-// SetPowerOnStart 设置通电自启动
+// SetPowerOnStart sets power-on auto-start
func (a *CoreApp) SetPowerOnStart(enabled bool) bool {
if !a.deviceManager.SetPowerOnStart(enabled) {
return false
@@ -1075,14 +1075,14 @@ func (a *CoreApp) SetPowerOnStart(enabled bool) bool {
cfg.PowerOnStart = enabled
a.configManager.Update(cfg)
- // 广播配置更新
+ // Broadcast config update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventConfigUpdate, cfg)
}
return true
}
-// SetSmartStartStop 设置智能启停
+// SetSmartStartStop sets smart start/stop
func (a *CoreApp) SetSmartStartStop(mode string) bool {
if !a.deviceManager.SetSmartStartStop(mode) {
return false
@@ -1092,14 +1092,14 @@ func (a *CoreApp) SetSmartStartStop(mode string) bool {
cfg.SmartStartStop = mode
a.configManager.Update(cfg)
- // 广播配置更新
+ // Broadcast config update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventConfigUpdate, cfg)
}
return true
}
-// SetBrightness 设置亮度
+// SetBrightness sets the brightness
func (a *CoreApp) SetBrightness(percentage int) bool {
if !a.deviceManager.SetBrightness(percentage) {
return false
@@ -1109,14 +1109,14 @@ func (a *CoreApp) SetBrightness(percentage int) bool {
cfg.Brightness = percentage
a.configManager.Update(cfg)
- // 广播配置更新
+ // Broadcast config update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventConfigUpdate, cfg)
}
return true
}
-// SetLightStrip 设置灯带
+// SetLightStrip sets the light strip configuration
func (a *CoreApp) SetLightStrip(lightCfg types.LightStripConfig) error {
lightCfg, _ = normalizeLightStripConfig(lightCfg)
@@ -1148,7 +1148,7 @@ func (a *CoreApp) applyConfiguredLightStrip() error {
cfg.LightStrip = lightCfg
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存灯带默认配置失败: %v", err)
+ a.logError("Failed to save default light strip config: %v", err)
}
}
@@ -1225,9 +1225,9 @@ func normalizeManualGearMemoryConfig(cfg *types.AppConfig) bool {
changed = true
}
- for _, gear := range []string{"静音", "标准", "强劲", "超频"} {
+ for _, gear := range []string{"Silent", "Standard", "Performance", "Overclock"} {
if level, ok := cfg.ManualGearLevels[gear]; !ok {
- cfg.ManualGearLevels[gear] = "中"
+ cfg.ManualGearLevels[gear] = "Mid"
changed = true
} else {
normalized := normalizeManualLevel(level)
@@ -1259,7 +1259,7 @@ func (a *CoreApp) applyHotkeyBindings(cfg types.AppConfig) {
return
}
if err := a.hotkeyManager.UpdateBindings(cfg.ManualGearToggleHotkey, cfg.AutoControlToggleHotkey, cfg.CurveProfileToggleHotkey); err != nil {
- a.logError("更新全局快捷键失败: %v", err)
+ a.logError("Failed to update global hotkeys: %v", err)
}
}
@@ -1295,7 +1295,7 @@ func (a *CoreApp) handleHotkeyAction(action hotkeysvc.Action, shortcut string) {
}
default:
success = false
- message = "未知快捷键动作"
+ message = "Unknown hotkey action"
}
if a.ipcServer != nil {
@@ -1307,9 +1307,9 @@ func (a *CoreApp) handleHotkeyAction(action hotkeysvc.Action, shortcut string) {
})
}
- title := "BS2PRO 快捷键"
+ title := "BS2PRO Hotkey"
if !success {
- title = "BS2PRO 快捷键失败"
+ title = "BS2PRO Hotkey Failed"
}
if a.notifier != nil {
a.notifier.Notify(title, message)
@@ -1322,7 +1322,7 @@ func (a *CoreApp) toggleCurveProfileByHotkey() (string, error) {
if err != nil {
return "", err
}
- return fmt.Sprintf("温控曲线已切换: %s", profile.Name), nil
+ return fmt.Sprintf("Fan curve switched to: %s", profile.Name), nil
}
func (a *CoreApp) toggleAutoControlByHotkey() (string, error) {
@@ -1332,9 +1332,9 @@ func (a *CoreApp) toggleAutoControlByHotkey() (string, error) {
return "", err
}
if target {
- return "智能变频已开启", nil
+ return "Smart fan control enabled", nil
}
- return "智能变频已关闭", nil
+ return "Smart fan control disabled", nil
}
func (a *CoreApp) toggleManualGearByHotkey() (string, error) {
@@ -1342,24 +1342,24 @@ func (a *CoreApp) toggleManualGearByHotkey() (string, error) {
if cfg.AutoControl {
if err := a.SetAutoControl(false); err != nil {
- return "", fmt.Errorf("切换到手动模式失败: %w", err)
+ return "", fmt.Errorf("failed to switch to manual mode: %w", err)
}
}
nextGear, nextLevel := a.getNextManualGearWithMemory(cfg.ManualGear, cfg.ManualLevel)
if ok := a.SetManualGear(nextGear, nextLevel); !ok {
- return "", fmt.Errorf("应用手动挡位失败")
+ return "", fmt.Errorf("failed to apply manual gear")
}
rpm := getManualGearRPM(nextGear, nextLevel)
if rpm > 0 {
- return fmt.Sprintf("手动挡位: %s %s (%d RPM)", nextGear, nextLevel, rpm), nil
+ return fmt.Sprintf("Manual gear: %s %s (%d RPM)", nextGear, nextLevel, rpm), nil
}
- return fmt.Sprintf("手动挡位: %s %s", nextGear, nextLevel), nil
+ return fmt.Sprintf("Manual gear: %s %s", nextGear, nextLevel), nil
}
func (a *CoreApp) getNextManualGearWithMemory(currentGear, currentLevel string) (string, string) {
- sequence := []string{"静音", "标准", "强劲", "超频"}
+ sequence := []string{"Silent", "Standard", "Performance", "Overclock"}
nextIndex := 0
for i, gear := range sequence {
@@ -1377,17 +1377,17 @@ func (a *CoreApp) getNextManualGearWithMemory(currentGear, currentLevel string)
}
func normalizeManualLevel(level string) string {
- if level == "低" || level == "中" || level == "高" {
+ if level == "Low" || level == "Mid" || level == "High" {
return level
}
- return "中"
+ return "Mid"
}
func cloneManualGearLevels(source map[string]string) map[string]string {
cloned := map[string]string{}
- for _, gear := range []string{"静音", "标准", "强劲", "超频"} {
+ for _, gear := range []string{"Silent", "Standard", "Performance", "Overclock"} {
if source == nil {
- cloned[gear] = "中"
+ cloned[gear] = "Mid"
continue
}
cloned[gear] = normalizeManualLevel(source[gear])
@@ -1409,7 +1409,7 @@ func (a *CoreApp) syncManualGearLevelMemoryLocked(cfg types.AppConfig) {
}
defaultLevel := normalizeManualLevel(cfg.ManualLevel)
- for _, gear := range []string{"静音", "标准", "强劲", "超频"} {
+ for _, gear := range []string{"Silent", "Standard", "Performance", "Overclock"} {
if fromCfg, ok := cfg.ManualGearLevels[gear]; ok {
a.manualGearLevelMemory[gear] = normalizeManualLevel(fromCfg)
continue
@@ -1421,7 +1421,7 @@ func (a *CoreApp) syncManualGearLevelMemoryLocked(cfg types.AppConfig) {
}
func (a *CoreApp) rememberManualGearLevel(gear, level string) {
- if gear != "静音" && gear != "标准" && gear != "强劲" && gear != "超频" {
+ if gear != "Silent" && gear != "Standard" && gear != "Performance" && gear != "Overclock" {
return
}
@@ -1454,9 +1454,9 @@ func getManualGearRPM(gear, level string) int {
}
for _, cmd := range commands {
- if (level == "低" && containsLevel(cmd.Name, "低")) ||
- (level == "中" && containsLevel(cmd.Name, "中")) ||
- (level == "高" && containsLevel(cmd.Name, "高")) {
+ if (level == "Low" && containsLevel(cmd.Name, "Low")) ||
+ (level == "Mid" && containsLevel(cmd.Name, "Mid")) ||
+ (level == "High" && containsLevel(cmd.Name, "High")) {
return cmd.RPM
}
}
@@ -1468,7 +1468,7 @@ func containsLevel(name, level string) bool {
return strings.Contains(name, level)
}
-// SetWindowsAutoStart 设置Windows自启动
+// SetWindowsAutoStart sets Windows auto-start
func (a *CoreApp) SetWindowsAutoStart(enable bool) error {
err := a.autostartManager.SetWindowsAutoStart(enable)
if err == nil {
@@ -1476,7 +1476,7 @@ func (a *CoreApp) SetWindowsAutoStart(enable bool) error {
cfg.WindowsAutoStart = enable
a.configManager.Update(cfg)
- // 广播配置更新
+ // Broadcast config update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventConfigUpdate, cfg)
}
@@ -1484,7 +1484,7 @@ func (a *CoreApp) SetWindowsAutoStart(enable bool) error {
return err
}
-// GetDebugInfo 获取调试信息
+// GetDebugInfo gets debug information
func (a *CoreApp) GetDebugInfo() map[string]any {
info := map[string]any{
"debugMode": a.debugMode,
@@ -1499,7 +1499,7 @@ func (a *CoreApp) GetDebugInfo() map[string]any {
return info
}
-// SetDebugMode 设置调试模式
+// SetDebugMode sets debug mode
func (a *CoreApp) SetDebugMode(enabled bool) error {
a.mutex.Lock()
defer a.mutex.Unlock()
@@ -1512,9 +1512,9 @@ func (a *CoreApp) SetDebugMode(enabled bool) error {
if a.logger != nil {
a.logger.SetDebugMode(enabled)
if enabled {
- a.logger.Info("调试模式已开启,后续日志将包含调试级别")
+ a.logger.Info("Debug mode enabled, subsequent logs will include debug level")
} else {
- a.logger.Info("调试模式已关闭,调试级别日志将被忽略")
+ a.logger.Info("Debug mode disabled, debug level logs will be suppressed")
}
}
@@ -1541,13 +1541,13 @@ func (a *CoreApp) stopTemperatureMonitoring() {
}
}
-// startTemperatureMonitoring 开始温度监控
+// startTemperatureMonitoring starts temperature monitoring
func (a *CoreApp) startTemperatureMonitoring() {
if a.monitoringTemp {
return
}
- // 清理可能残留的停止信号,避免新监控循环被立即中断。
+ // Clear any residual stop signals to avoid the new monitoring loop being interrupted immediately.
select {
case <-a.stopMonitoring:
default:
@@ -1555,14 +1555,14 @@ func (a *CoreApp) startTemperatureMonitoring() {
a.monitoringTemp = true
- // 注意:不在此处立即调用 EnterAutoMode,因为在启动时温度数据(桥接程序)可能尚未就绪。
- // 如果在温度读取成功之前切换到软件控制模式,设备将不会收到转速指令,导致风扇停转。
- // EnterAutoMode 和转速设置会在首次成功读取温度后,由 SetFanSpeed 内部统一完成。
+ // Note: Do not call EnterAutoMode here immediately, because temperature data (bridge program) may not be ready at startup.
+ // If we switch to software control mode before temperature is successfully read, the device won't receive speed commands, causing the fan to stop.
+ // EnterAutoMode and speed settings will be handled internally by SetFanSpeed after the first successful temperature read.
cfg := a.configManager.Get()
updateInterval := time.Duration(cfg.TempUpdateRate) * time.Second
- // 温度采样缓冲区
+ // Temperature sample buffer
sampleCount := max(cfg.TempSampleCount, 1)
tempSamples := make([]int, 0, sampleCount)
recentAvgTemps := make([]int, 0, 24)
@@ -1585,7 +1585,7 @@ func (a *CoreApp) startTemperatureMonitoring() {
a.currentTemp = temp
a.mutex.Unlock()
- // 广播温度更新
+ // Broadcast temperature update
if a.ipcServer != nil {
a.ipcServer.BroadcastEvent(ipc.EventTemperatureUpdate, temp)
}
@@ -1596,25 +1596,25 @@ func (a *CoreApp) startTemperatureMonitoring() {
cfg.SmartControl = smartCfg
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存智能控温配置失败: %v", err)
+ a.logError("Failed to save smart control config: %v", err)
}
}
if cfg.AutoControl && temp.MaxTemp > 0 {
- // 更新采样配置
+ // Update sample config
newSampleCount := max(cfg.TempSampleCount, 1)
if newSampleCount != sampleCount {
sampleCount = newSampleCount
tempSamples = make([]int, 0, sampleCount)
}
- // 添加新采样
+ // Add new sample
tempSamples = append(tempSamples, temp.MaxTemp)
if len(tempSamples) > sampleCount {
tempSamples = tempSamples[len(tempSamples)-sampleCount:]
}
- // 计算平均温度
+ // Calculate average temperature
avgTemp := 0
for _, t := range tempSamples {
avgTemp += t
@@ -1687,7 +1687,7 @@ func (a *CoreApp) startTemperatureMonitoring() {
if learningDirty && time.Since(lastLearningSave) >= 25*time.Second {
if err := a.configManager.Save(); err != nil {
- a.logError("保存学习曲线失败: %v", err)
+ a.logError("Failed to save learned curve: %v", err)
} else {
lastLearningSave = time.Now()
learningDirty = false
@@ -1699,7 +1699,7 @@ func (a *CoreApp) startTemperatureMonitoring() {
}
if baseRPM > 0 {
- a.logDebug("智能控温: 温度=%d°C 平均=%d°C 控制温度=%d°C 基础=%dRPM 目标=%dRPM", temp.MaxTemp, avgTemp, controlTemp, baseRPM, targetRPM)
+ a.logDebug("Smart control: temp=%d°C avg=%d°C control_temp=%d°C base=%dRPM target=%dRPM", temp.MaxTemp, avgTemp, controlTemp, baseRPM, targetRPM)
}
lastControlTemp = controlTemp
@@ -1709,14 +1709,14 @@ func (a *CoreApp) startTemperatureMonitoring() {
if learningDirty {
if err := a.configManager.Save(); err != nil {
- a.logError("退出监控时保存学习曲线失败: %v", err)
+ a.logError("Failed to save learned curve when exiting monitoring: %v", err)
}
}
}
-// startHealthMonitoring 启动健康监控
+// startHealthMonitoring starts health monitoring
func (a *CoreApp) startHealthMonitoring() {
- a.logInfo("启动健康监控系统")
+ a.logInfo("Starting health monitoring system")
a.healthCheckTicker = time.NewTicker(30 * time.Second)
@@ -1728,7 +1728,7 @@ func (a *CoreApp) startHealthMonitoring() {
case <-a.healthCheckTicker.C:
a.performHealthCheck()
case <-a.cleanupChan:
- a.logInfo("健康监控系统已停止")
+ a.logInfo("Health monitoring system stopped")
return
}
}
@@ -1741,46 +1741,46 @@ func (a *CoreApp) startHealthMonitoring() {
}
}
-// performHealthCheck 执行健康检查
+// performHealthCheck performs a health check
func (a *CoreApp) performHealthCheck() {
defer func() {
if r := recover(); r != nil {
- a.logError("健康检查中发生panic: %v", r)
+ a.logError("Panic during health check: %v", r)
}
}()
a.trayManager.CheckHealth()
a.checkDeviceHealth()
- a.logDebug("健康检查完成 - 托盘:%v 设备连接:%v",
+ a.logDebug("Health check completed - tray:%v device_connected:%v",
a.trayManager.IsInitialized(), a.isConnected)
}
-// checkDeviceHealth 检查设备健康状态
+// checkDeviceHealth checks device health status
func (a *CoreApp) checkDeviceHealth() {
a.mutex.RLock()
connected := a.isConnected
a.mutex.RUnlock()
if !connected {
- a.logInfo("健康检查: 设备未连接,尝试重新连接")
+ a.logInfo("Health check: device not connected, attempting reconnect")
a.safeGo("healthReconnect", func() {
if a.ConnectDevice() {
- a.logInfo("健康检查: 设备重连成功")
+ a.logInfo("Health check: device reconnected successfully")
} else {
- a.logDebug("健康检查: 设备重连失败,等待下次检查")
+ a.logDebug("Health check: device reconnect failed, waiting for next check")
}
})
} else {
- // 验证设备实际连接状态
+ // Verify actual device connection status
if !a.deviceManager.IsConnected() {
- a.logError("健康检查: 检测到设备状态不一致,触发断开回调")
+ a.logError("Health check: detected inconsistent device status, triggering disconnect callback")
a.onDeviceDisconnect()
}
}
}
-// cleanup 清理资源
+// cleanup cleans up resources
func (a *CoreApp) cleanup() {
if a.healthCheckTicker != nil {
a.healthCheckTicker.Stop()
@@ -1792,12 +1792,12 @@ func (a *CoreApp) cleanup() {
}
if a.logger != nil {
- a.logger.Info("核心服务正在退出,清理资源")
+ a.logger.Info("Core service exiting, cleaning up resources")
a.logger.Close()
}
}
-// 日志辅助方法
+// Logging helper methods
func (a *CoreApp) logInfo(format string, v ...any) {
if a.logger != nil {
a.logger.Info(format, v...)
@@ -1828,11 +1828,11 @@ func (a *CoreApp) safeGo(name string, fn func()) {
}()
}
-// launchGUI 启动 GUI 程序
+// launchGUI launches the GUI program
func launchGUI() error {
exePath, err := os.Executable()
if err != nil {
- return fmt.Errorf("获取可执行文件路径失败: %v", err)
+ return fmt.Errorf("failed to get executable path: %v", err)
}
exeDir := filepath.Dir(exePath)
@@ -1841,7 +1841,7 @@ func launchGUI() error {
if _, err := os.Stat(guiPath); os.IsNotExist(err) {
guiPath = filepath.Join(exeDir, "..", "BS2PRO-Controller.exe")
if _, err := os.Stat(guiPath); os.IsNotExist(err) {
- return fmt.Errorf("GUI 程序不存在: %s", guiPath)
+ return fmt.Errorf("GUI program not found: %s", guiPath)
}
}
@@ -1851,11 +1851,11 @@ func launchGUI() error {
}
if err := cmd.Start(); err != nil {
- return fmt.Errorf("启动 GUI 程序失败: %v", err)
+ return fmt.Errorf("failed to launch GUI program: %v", err)
}
- // 使用 fmt 而非日志系统,避免循环依赖
- fmt.Printf("GUI 程序已启动,PID: %d\n", cmd.Process.Pid)
+ // Use fmt instead of logging system to avoid circular dependency
+ fmt.Printf("GUI program launched, PID: %d\n", cmd.Process.Pid)
go func() {
cmd.Wait()
diff --git a/cmd/core/crash_report.go b/cmd/core/crash_report.go
index b4fe80c..3aece97 100644
--- a/cmd/core/crash_report.go
+++ b/cmd/core/crash_report.go
@@ -16,8 +16,8 @@ func capturePanic(app *CoreApp, source string, recovered any) string {
logDir := resolveCrashLogDir(app)
if err := os.MkdirAll(logDir, 0755); err != nil {
- fmt.Fprintf(os.Stderr, "创建崩溃日志目录失败: %v\n", err)
- fmt.Fprintf(os.Stderr, "panic来源: %s, panic: %v\n%s\n", source, recovered, string(stack))
+ fmt.Fprintf(os.Stderr, "Failed to create crash log directory: %v\n", err)
+ fmt.Fprintf(os.Stderr, "panic source: %s, panic: %v\n%s\n", source, recovered, string(stack))
return ""
}
@@ -36,20 +36,20 @@ func capturePanic(app *CoreApp, source string, recovered any) string {
builder.WriteString("\n")
if err := os.WriteFile(filePath, []byte(builder.String()), 0644); err != nil {
- fmt.Fprintf(os.Stderr, "写入崩溃报告失败: %v\n", err)
- fmt.Fprintf(os.Stderr, "panic来源: %s, panic: %v\n%s\n", source, recovered, string(stack))
+ fmt.Fprintf(os.Stderr, "Failed to write crash report: %v\n", err)
+ fmt.Fprintf(os.Stderr, "panic source: %s, panic: %v\n%s\n", source, recovered, string(stack))
return ""
}
if app != nil {
- app.logError("[%s] 捕获到panic: %v", source, recovered)
- app.logError("[%s] panic堆栈:\n%s", source, string(stack))
+ app.logError("[%s] Caught panic: %v", source, recovered)
+ app.logError("[%s] Panic stack trace:\n%s", source, string(stack))
if app.logger != nil {
app.logger.Close()
}
}
- fmt.Fprintf(os.Stderr, "程序发生panic,崩溃报告已写入: %s\n", filePath)
+ fmt.Fprintf(os.Stderr, "Program panic occurred, crash report written to: %s\n", filePath)
return filePath
}
diff --git a/cmd/core/curve_profiles.go b/cmd/core/curve_profiles.go
index 4f4fa72..5bbe6dd 100644
--- a/cmd/core/curve_profiles.go
+++ b/cmd/core/curve_profiles.go
@@ -96,7 +96,7 @@ func normalizeCurveProfilesConfig(cfg *types.AppConfig) bool {
if len(cfg.FanCurveProfiles) == 0 {
cfg.FanCurveProfiles = []types.FanCurveProfile{{
ID: "default",
- Name: "默认",
+ Name: "Default",
Curve: cloneFanCurve(baseCurve),
}}
changed = true
@@ -112,7 +112,7 @@ func normalizeCurveProfilesConfig(cfg *types.AppConfig) bool {
}
seenIDs[profile.ID] = true
- fallbackName := fmt.Sprintf("方案%d", i+1)
+ fallbackName := fmt.Sprintf("Profile%d", i+1)
name := normalizeCurveProfileName(profile.Name, fallbackName)
if name != profile.Name {
profile.Name = name
@@ -134,7 +134,7 @@ func normalizeCurveProfilesConfig(cfg *types.AppConfig) bool {
if len(cfg.FanCurveProfiles) == 0 {
cfg.FanCurveProfiles = []types.FanCurveProfile{{
ID: "default",
- Name: "默认",
+ Name: "Default",
Curve: cloneFanCurve(baseCurve),
}}
changed = true
@@ -200,7 +200,7 @@ func (a *CoreApp) GetFanCurveProfiles() types.FanCurveProfilesPayload {
if normalizeCurveProfilesConfig(&cfg) {
a.configManager.Set(cfg)
if err := a.configManager.Save(); err != nil {
- a.logError("保存温控曲线方案默认配置失败: %v", err)
+ a.logError("Failed to save default fan curve profile config: %v", err)
}
}
return a.fanCurveProfilesPayloadFromConfig(cfg)
@@ -215,7 +215,7 @@ func (a *CoreApp) SetActiveFanCurveProfile(profileID string) (types.FanCurveProf
idx := findCurveProfileIndex(cfg.FanCurveProfiles, profileID)
if idx < 0 {
- return types.FanCurveProfile{}, fmt.Errorf("未找到温控曲线方案")
+ return types.FanCurveProfile{}, fmt.Errorf("fan curve profile not found")
}
cfg.ActiveFanCurveProfileID = cfg.FanCurveProfiles[idx].ID
@@ -238,7 +238,7 @@ func (a *CoreApp) CycleFanCurveProfile() (types.FanCurveProfile, error) {
normalizeCurveProfilesConfig(&cfg)
if len(cfg.FanCurveProfiles) == 0 {
- return types.FanCurveProfile{}, fmt.Errorf("暂无可用温控曲线方案")
+ return types.FanCurveProfile{}, fmt.Errorf("no fan curve profiles available")
}
idx := max(findCurveProfileIndex(cfg.FanCurveProfiles, cfg.ActiveFanCurveProfileID), 0)
@@ -269,7 +269,7 @@ func (a *CoreApp) SaveFanCurveProfile(params ipc.SaveFanCurveProfileParams) (typ
return types.FanCurveProfile{}, err
}
- profileName := normalizeCurveProfileName(params.Name, "新曲线")
+ profileName := normalizeCurveProfileName(params.Name, "New")
profileID := strings.TrimSpace(params.ID)
idx := findCurveProfileIndex(cfg.FanCurveProfiles, profileID)
if idx < 0 {
@@ -306,17 +306,17 @@ func (a *CoreApp) DeleteFanCurveProfile(profileID string) error {
normalizeCurveProfilesConfig(&cfg)
if len(cfg.FanCurveProfiles) <= 1 {
- return fmt.Errorf("至少保留一个温控曲线方案")
+ return fmt.Errorf("at least one fan curve profile must be kept")
}
idx := findCurveProfileIndex(cfg.FanCurveProfiles, profileID)
if idx < 0 {
- return fmt.Errorf("未找到温控曲线方案")
+ return fmt.Errorf("fan curve profile not found")
}
cfg.FanCurveProfiles = append(cfg.FanCurveProfiles[:idx], cfg.FanCurveProfiles[idx+1:]...)
if len(cfg.FanCurveProfiles) == 0 {
- return fmt.Errorf("至少保留一个温控曲线方案")
+ return fmt.Errorf("at least one fan curve profile must be kept")
}
if cfg.ActiveFanCurveProfileID == profileID {
@@ -366,35 +366,35 @@ func (a *CoreApp) ExportFanCurveProfiles() (string, error) {
func (a *CoreApp) ImportFanCurveProfiles(code string) error {
trimmed := strings.TrimSpace(code)
if trimmed == "" {
- return fmt.Errorf("导入字符串不能为空")
+ return fmt.Errorf("import string cannot be empty")
}
if !strings.HasPrefix(trimmed, curveExportPrefix) {
- return fmt.Errorf("导入字符串格式错误")
+ return fmt.Errorf("invalid import string format")
}
raw := strings.TrimPrefix(trimmed, curveExportPrefix)
compressed, err := base64.RawURLEncoding.DecodeString(raw)
if err != nil {
- return fmt.Errorf("导入字符串解码失败")
+ return fmt.Errorf("failed to decode import string")
}
zr, err := zlib.NewReader(bytes.NewReader(compressed))
if err != nil {
- return fmt.Errorf("导入字符串解压失败")
+ return fmt.Errorf("failed to decompress import string")
}
defer zr.Close()
plain, err := io.ReadAll(zr)
if err != nil {
- return fmt.Errorf("导入数据读取失败")
+ return fmt.Errorf("failed to read import data")
}
var payload fanCurveExportPayload
if err := json.Unmarshal(plain, &payload); err != nil {
- return fmt.Errorf("导入数据格式错误")
+ return fmt.Errorf("invalid import data format")
}
if payload.V != 1 {
- return fmt.Errorf("不支持的导入版本")
+ return fmt.Errorf("unsupported import version")
}
a.mutex.Lock()
diff --git a/cmd/core/main.go b/cmd/core/main.go
index 29c525b..54dcca5 100644
--- a/cmd/core/main.go
+++ b/cmd/core/main.go
@@ -29,7 +29,7 @@ func main() {
}
}()
- // 检测命令行参数
+ // Parse command line arguments
debugMode := false
isAutoStart := false
@@ -42,23 +42,23 @@ func main() {
}
}
- // 创建核心应用
+ // Create core application
app = NewCoreApp(debugMode, isAutoStart)
- // 启动应用
+ // Start application
if err := app.Start(); err != nil {
- panic(fmt.Sprintf("启动核心服务失败: %v", err))
+ panic(fmt.Sprintf("Failed to start core service: %v", err))
}
- // 等待退出信号
+ // Wait for exit signal
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
select {
case <-sigChan:
- app.logInfo("收到系统退出信号")
+ app.logInfo("Received system exit signal")
case <-app.quitChan:
- app.logInfo("收到应用退出请求")
+ app.logInfo("Received application quit request")
}
app.Stop()
diff --git a/frontend/src/app/components/AppFatalError.tsx b/frontend/src/app/components/AppFatalError.tsx
index cd2a927..007b35a 100644
--- a/frontend/src/app/components/AppFatalError.tsx
+++ b/frontend/src/app/components/AppFatalError.tsx
@@ -12,12 +12,12 @@ export default function AppFatalError({ message, onRetry }: AppFatalErrorProps)
-
应用初始化失败
+
App Initialization Failed
{message}
- 重试
+ Retry
diff --git a/frontend/src/app/components/AppShell.tsx b/frontend/src/app/components/AppShell.tsx
index 36b5c7e..fa33349 100644
--- a/frontend/src/app/components/AppShell.tsx
+++ b/frontend/src/app/components/AppShell.tsx
@@ -18,9 +18,9 @@ import {
import { types } from '../../../wailsjs/go/models';
const TAB_ITEMS = [
- { id: 'status', title: '状态', icon: LayoutGrid },
- { id: 'curve', title: '曲线', icon: LineChart },
- { id: 'control', title: '设置', icon: Settings2 },
+ { id: 'status', title: 'Status', icon: LayoutGrid },
+ { id: 'curve', title: 'Curve', icon: LineChart },
+ { id: 'control', title: 'Settings', icon: Settings2 },
] as const;
type ActiveTab = (typeof TAB_ITEMS)[number]['id'];
@@ -117,7 +117,7 @@ export default function AppShell({
}`}
>
{isConnected ? : }
- {isConnected ? '设备已连接' : '设备离线'}
+ {isConnected ? 'Device Connected' : 'Device Offline'}
- {autoControl ? '智能控制开启' : '手动模式'}
+ {autoControl ? 'Smart Control On' : 'Manual Mode'}
@@ -221,7 +221,7 @@ export default function AppShell({
{bridgeWarning}
diff --git a/frontend/src/app/components/ControlPanel.tsx b/frontend/src/app/components/ControlPanel.tsx
index cf3c0d5..bc2393f 100644
--- a/frontend/src/app/components/ControlPanel.tsx
+++ b/frontend/src/app/components/ControlPanel.tsx
@@ -252,7 +252,7 @@ function HotkeyField({
{value && (
e.preventDefault()}
onClick={onClear}
className="absolute right-2 top-1/2 -translate-y-1/2 rounded-full p-0.5 text-muted-foreground transition hover:bg-muted hover:text-foreground"
@@ -332,7 +332,7 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
setLatestReleaseUrl(data?.html_url || 'https://github.com/TIANLI0/BS2PRO-Controller/releases/latest');
setLatestReleaseBody(typeof data?.body === 'string' ? data.body.trim() : '');
} catch {
- setReleaseError('检查更新失败,请稍后重试');
+ setReleaseError('Failed to check for updates, please try again later');
setLatestReleaseTag('');
setLatestReleaseUrl('https://github.com/TIANLI0/BS2PRO-Controller/releases/latest');
setLatestReleaseBody('');
@@ -396,7 +396,7 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
if (enabled) await apiService.setAutoStartWithMethod(true, isAdmin ? 'task_scheduler' : 'registry');
else await apiService.setAutoStartWithMethod(false, '');
onConfigChange(types.AppConfig.createFrom({ ...config, windowsAutoStart: enabled }));
- } catch (e) { alert(`设置自启动失败: ${e}`); } finally { setLoading('windowsAutoStart', false); }
+ } catch (e) { alert(`Failed to set auto-start: ${e}`); } finally { setLoading('windowsAutoStart', false); }
}, [config, onConfigChange]);
const handleIgnoreDeviceOnReconnectChange = useCallback(async (enabled: boolean) => {
@@ -461,9 +461,9 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
const latest = await apiService.getConfig();
onConfigChange(types.AppConfig.createFrom(latest));
await loadCurveProfiles();
- toast.success('温控曲线已切换');
+ toast.success('Fan curve profile switched');
} catch (e) {
- toast.error(`切换曲线失败: ${e}`);
+ toast.error(`Failed to switch curve: ${e}`);
} finally {
setCurveProfileLoading(false);
}
@@ -483,39 +483,39 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
/* ── Options data ── */
const smartStartStopOptions = [
- { value: 'off', label: '关闭', description: '禁用智能启停功能' },
- { value: 'immediate', label: '即时', description: '立即响应系统负载变化' },
- { value: 'delayed', label: '延时', description: '延时响应,避免频繁启停' },
+ { value: 'off', label: 'Off', description: 'Disable smart start/stop' },
+ { value: 'immediate', label: 'Immediate', description: 'Respond to system load changes immediately' },
+ { value: 'delayed', label: 'Delayed', description: 'Delayed response to avoid frequent start/stop' },
];
const sampleCountOptions = [
- { value: 1, label: '1次 (即时)' },
- { value: 2, label: '2次 (2s)' },
- { value: 3, label: '3次 (3s)' },
- { value: 5, label: '5次 (5s)' },
- { value: 10, label: '10次 (10s)' },
+ { value: 1, label: '1x (Instant)' },
+ { value: 2, label: '2x (2s)' },
+ { value: 3, label: '3x (3s)' },
+ { value: 5, label: '5x (5s)' },
+ { value: 10, label: '10x (10s)' },
];
const lightModeOptions = [
- { value: 'off', label: '关闭灯光', description: '关闭所有RGB灯光' },
- { value: 'smart_temp', label: '智能温控', description: '根据温度自动切换灯效' },
- { value: 'static_single', label: '单色常亮', description: '固定单色显示' },
- { value: 'static_multi', label: '多色常亮', description: '三色静态分区' },
- { value: 'rotation', label: '多色旋转', description: '颜色循环旋转' },
- { value: 'flowing', label: '流光', description: '预设流光效果' },
- { value: 'breathing', label: '呼吸', description: '多色呼吸变化' },
+ { value: 'off', label: 'Lights Off', description: 'Turn off all RGB lights' },
+ { value: 'smart_temp', label: 'Smart Temp', description: 'Auto-switch light effects based on temperature' },
+ { value: 'static_single', label: 'Single Color', description: 'Fixed single color display' },
+ { value: 'static_multi', label: 'Multi Color', description: 'Three-color static zones' },
+ { value: 'rotation', label: 'Color Rotation', description: 'Cycling color rotation' },
+ { value: 'flowing', label: 'Flowing', description: 'Preset flowing effect' },
+ { value: 'breathing', label: 'Breathing', description: 'Multi-color breathing effect' },
];
const lightSpeedOptions = [
- { value: 'fast', label: '快速' },
- { value: 'medium', label: '中速' },
- { value: 'slow', label: '慢速' },
+ { value: 'fast', label: 'Fast' },
+ { value: 'medium', label: 'Medium' },
+ { value: 'slow', label: 'Slow' },
];
const lightColorPresets = [
- { name: '霓虹', colors: [{ r: 255, g: 0, b: 128 }, { r: 0, g: 255, b: 255 }, { r: 128, g: 0, b: 255 }] },
- { name: '森林', colors: [{ r: 86, g: 169, b: 84 }, { r: 161, g: 210, b: 106 }, { r: 44, g: 120, b: 115 }] },
- { name: '冰川', colors: [{ r: 80, g: 170, b: 255 }, { r: 116, g: 214, b: 255 }, { r: 200, g: 240, b: 255 }] },
+ { name: 'Neon', colors: [{ r: 255, g: 0, b: 128 }, { r: 0, g: 255, b: 255 }, { r: 128, g: 0, b: 255 }] },
+ { name: 'Forest', colors: [{ r: 86, g: 169, b: 84 }, { r: 161, g: 210, b: 106 }, { r: 44, g: 120, b: 115 }] },
+ { name: 'Glacier', colors: [{ r: 80, g: 170, b: 255 }, { r: 116, g: 214, b: 255 }, { r: 200, g: 240, b: 255 }] },
];
const requiredColorCount = getRequiredColorCount(lightStripConfig.mode);
@@ -540,7 +540,7 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
});
await apiService.setLightStrip(submitConfig);
onConfigChange(types.AppConfig.createFrom({ ...config, lightStrip: submitConfig }));
- } catch (e) { alert(`设置灯带失败: ${e}`); } finally { setLoading('lightStrip', false); }
+ } catch (e) { alert(`Failed to set light strip: ${e}`); } finally { setLoading('lightStrip', false); }
}, [lightStripConfig, config, onConfigChange, requiredColorCount]);
const saveHotkeys = useCallback(async (silent = false) => {
@@ -552,7 +552,7 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
const uniq = new Set([manualValue, autoValue, curveValue]);
if (uniq.size !== 3) {
- if (!silent) toast.error('三个快捷键不能设置为同一个组合');
+ if (!silent) toast.error('The three hotkeys cannot be set to the same combination');
return false;
}
@@ -564,10 +564,10 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
});
await apiService.updateConfig(newCfg);
onConfigChange(newCfg);
- if (!silent) toast.success('快捷键保存成功');
+ if (!silent) toast.success('Hotkeys saved successfully');
return true;
} catch (e) {
- if (!silent) toast.error(`保存快捷键失败: ${e}`);
+ if (!silent) toast.error(`Failed to save hotkeys: ${e}`);
return false;
} finally {
setLoading('hotkeys', false);
@@ -617,11 +617,11 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
-
实时概览
+ Real-time Overview
-
当前温度
+
Current Temp
80 ? 'text-red-500' : (temperature?.maxTemp ?? 0) > 70 ? 'text-amber-500' : 'text-primary'
@@ -631,20 +631,20 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
CPU {temperature?.cpuTemp ?? '--'}°C · GPU {temperature?.gpuTemp ?? '--'}°C
-
实时转速
+
Current Speed
{fanData?.currentRpm ?? '--'} RPM
{fanData?.workMode ?? '--'}
-
目标转速
+
Target Speed
{fanData?.targetRpm ?? '--'} RPM
-
挡位 {fanData?.setGear ?? '--'}
+
Gear {fanData?.setGear ?? '--'}
- {/* ═══════════ 1. 灯光效果 ═══════════ */}
-
+ {/* ═══════════ 1. Light Effects ═══════════ */}
+
setLightStripConfig(types.LightStripConfig.createFrom({ ...lightStripConfig, mode: v as string }))}
options={lightModeOptions}
size="sm"
- label="效果模式"
+ label="Effect Mode"
/>
setLightStripConfig(types.LightStripConfig.createFrom({ ...lightStripConfig, speed: v as string }))}
options={lightSpeedOptions}
size="sm"
- label="动画速度"
+ label="Animation Speed"
disabled={['off', 'smart_temp', 'static_single', 'static_multi'].includes(lightStripConfig.mode)}
/>
@@ -668,14 +668,14 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
min={0} max={100} step={1}
value={lightStripConfig.brightness}
onChange={(v) => setLightStripConfig(types.LightStripConfig.createFrom({ ...lightStripConfig, brightness: v }))}
- label="亮度"
+ label="Brightness"
valueFormatter={(v) => `${v}%`}
disabled={lightStripConfig.mode === 'off' || lightStripConfig.mode === 'smart_temp'}
/>
{lightStripConfig.mode === 'smart_temp' && (
- 智能温控模式由设备自动控制灯效,不支持手动调节颜色与亮度。
+ Smart temp mode automatically controls light effects on the device. Manual color and brightness adjustment is not supported.
)}
@@ -703,7 +703,7 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
- {/* ═══════════ 2. 风扇控制 ═══════════ */}
-
+ {/* ═══════════ 2. Fan Control ═══════════ */}
+
{/* Auto control */}
: }
- title="自动温度控制"
- description="根据温度曲线自动调节风扇转速"
+ title="Auto Temperature Control"
+ description="Automatically adjust fan speed based on temperature curve"
disabled={(config as any).customSpeedEnabled}
>
}
- title="采样时间"
- description="降低频繁调整带来的轴噪,不知道默认即可"
+ title="Sample Time"
+ description="Reduce bearing noise from frequent adjustments. Leave default if unsure."
>
}
- title="曲线方案"
- description="在设置页直接切换风扇温控曲线,避免来回切页。"
+ title="Curve Profile"
+ description="Switch fan temperature curve directly from settings without switching tabs."
>
-
自定义转速
-
固定转速,适合特殊场景
+
Custom Speed
+
Fixed speed, suitable for special scenarios
handleCustomSpeedApply(true, customSpeedInput)} className="bg-amber-600 hover:bg-amber-700 text-white">
- 应用
+ Apply
- ⚠ 自定义转速会禁用智能温控
+ ⚠ Custom speed will disable smart temperature control
)}
@@ -841,12 +841,12 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
- {/* ═══════════ 3. 设备设置 ═══════════ */}
-
+ {/* ═══════════ 3. Device Settings ═══════════ */}
+
}
- title="挡位灯"
- description="控制设备上的挡位指示灯"
+ title="Gear Light"
+ description="Control the gear indicator light on the device"
disabled={!isConnected}
>
}
- title="通电自启动"
- description="设备通电后自动运行"
+ title="Power-on Auto Start"
+ description="Automatically run when device is powered on"
disabled={!isConnected}
>
}
- title="智能启停"
- description="系统关闭后何时停止散热"
+ title="Smart Start/Stop"
+ description="When to stop cooling after system shutdown"
disabled={!isConnected}
>
@@ -891,24 +891,24 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
- {/* ═══════════ 4. 系统设置 ═══════════ */}
-
+ {/* ═══════════ 4. System Settings ═══════════ */}
+
-
全局快捷键
+
Global Hotkeys
- 点击右侧输入框后直接按组合键,失焦自动保存,按 Backspace/Delete 或清除按钮恢复默认值。
+ Click the input field and press a key combination. Auto-saves on blur. Press Backspace/Delete or the clear button to reset to default.
setRecordingTarget('manual')}
onBlur={handleHotkeyInputBlur}
@@ -919,10 +919,10 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
setRecordingTarget('auto')}
onBlur={handleHotkeyInputBlur}
@@ -933,10 +933,10 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
setRecordingTarget('curve')}
onBlur={handleHotkeyInputBlur}
@@ -948,9 +948,9 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
}
- title="开机自启动"
- description="Windows 启动时自动运行"
- tip="以管理员身份运行可避免每次 UAC 授权"
+ title="Start on Boot"
+ description="Automatically run when Windows starts"
+ tip="Running as administrator avoids UAC prompts each time"
>
}
- title="断连保持配置"
- description="重连后继续使用 APP 配置"
- tip="推荐开启,防止断连后进入设备默认模式"
+ title="Keep Config on Disconnect"
+ description="Continue using app config after reconnection"
+ tip="Recommended to enable, prevents device from reverting to default mode after disconnect"
>
- 设备未连接,部分功能不可用
+ Device not connected, some features are unavailable
)}
- {/* ═══════════ 5. 关于与更新 ═══════════ */}
+ {/* ═══════════ 5. About & Updates ═══════════ */}
@@ -1017,13 +1017,13 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
{hasNewVersion && (
-
Release 日志
+
Release Notes
{latestReleaseBody ? (
{latestReleaseBody}
) : (
-
暂无日志内容,或本次获取失败。
+
No release notes available, or failed to fetch.
)}
)}
@@ -1031,23 +1031,23 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
-
开发者
+
Developer
Tianli
-
一个不知名开发者
+
An indie developer
@@ -1077,14 +1077,14 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
- {/* ═══════════ 6. 调试面板 ═══════════ */}
+ {/* ═══════════ 6. Debug Panel ═══════════ */}
- 调试面板
+ Debug Panel
@@ -1096,8 +1096,8 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
-
调试模式
-
启用详细日志
+
Debug Mode
+
Enable detailed logging
@@ -1107,15 +1107,15 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
{config.guiMonitoring ?
:
}
-
GUI 监控
-
监控 GUI 响应
+
GUI Monitoring
+
Monitor GUI response
- 刷新调试信息
+ Refresh Debug Info
{debugInfo && (
@@ -1151,25 +1151,25 @@ export default function ControlPanel({ config, onConfigChange, isConnected, fanD
- 风险提示
+ Risk Warning
-
启用后:
+
After enabling:
- • 智能温控将被禁用
- • 风扇以固定转速运行
- • 可能导致散热不足
+ • Smart temperature control will be disabled
+ • Fan will run at a fixed speed
+ • May result in insufficient cooling
-
设置转速
+
Set Speed
{customSpeedInput} RPM
setShowCustomSpeedWarning(false)} className="flex-1">
- 取消
+ Cancel
}
>
- 确认
+ Confirm
diff --git a/frontend/src/app/components/DeviceStatus.tsx b/frontend/src/app/components/DeviceStatus.tsx
index 8fab5f8..5a460b1 100644
--- a/frontend/src/app/components/DeviceStatus.tsx
+++ b/frontend/src/app/components/DeviceStatus.tsx
@@ -36,10 +36,10 @@ interface DeviceStatusProps {
}
const getTempStatus = (temp: number) => {
- if (temp > 85) return { color: 'text-red-500', bg: 'bg-red-500', label: '过热' };
- if (temp > 75) return { color: 'text-orange-500', bg: 'bg-orange-500', label: '偏高' };
- if (temp > 60) return { color: 'text-yellow-500', bg: 'bg-yellow-500', label: '正常' };
- return { color: 'text-green-500', bg: 'bg-green-500', label: '良好' };
+ if (temp > 85) return { color: 'text-red-500', bg: 'bg-red-500', label: 'Overheat' };
+ if (temp > 75) return { color: 'text-orange-500', bg: 'bg-orange-500', label: 'High' };
+ if (temp > 60) return { color: 'text-yellow-500', bg: 'bg-yellow-500', label: 'Normal' };
+ return { color: 'text-green-500', bg: 'bg-green-500', label: 'Good' };
};
const getFanSpinDuration = (rpm?: number) => {
@@ -112,7 +112,7 @@ const FanRpmDisplay = memo(function FanRpmDisplay({
RPM
- 目标 {targetRpm ?? '--'} · {setGear || '--'}
+ Target {targetRpm ?? '--'} · {setGear || '--'}
@@ -176,35 +176,35 @@ export default function DeviceStatus({
await apiService.setAutoControl(enabled);
onConfigChange(types.AppConfig.createFrom({ ...config, autoControl: enabled }));
} catch (err) {
- console.error('设置智能变频失败:', err);
+ console.error('Failed to set smart speed control:', err);
}
};
const normalizedProductId = deviceProductId?.trim().toUpperCase() ?? '';
const isProModel = normalizedProductId === '0X1002';
const isBs2Model = normalizedProductId === '0X1001';
- const deviceModel = isProModel ? 'BS2 PRO' : isBs2Model ? 'BS2' : '未知设备';
+ const deviceModel = isProModel ? 'BS2 PRO' : isBs2Model ? 'BS2' : 'Unknown Device';
const deviceImageSrc = isBs2Model ? '/bs2.png' : '/bs2pro.png';
- const modeTitle = config.autoControl ? '智能控制' : config.customSpeedEnabled ? '固定转速' : '手动策略';
+ const modeTitle = config.autoControl ? 'Smart Control' : config.customSpeedEnabled ? 'Fixed Speed' : 'Manual Strategy';
const modeDesc = config.autoControl
- ? '根据实时温度自动调节转速'
+ ? 'Auto-adjusts fan speed based on real-time temperature'
: config.customSpeedEnabled
- ? `当前固定为 ${config.customSpeedRPM || fanData?.currentRpm || '--'} RPM`
- : '可在设置页调整模式与参数';
+ ? `Currently fixed at ${config.customSpeedRPM || fanData?.currentRpm || '--'} RPM`
+ : 'Adjust mode and parameters in Settings';
const modeDisplayTitle = activeCurveProfileName ? `${modeTitle}(${activeCurveProfileName})` : modeTitle;
const fanSpinDuration = getFanSpinDuration(fanData?.currentRpm);
const maxRpmInfo = getReportedMaxRpm(fanData?.gearSettings, fanData?.maxGear);
const maxGearHighLevelRpm = maxRpmInfo.rpm;
const maxRpmHint =
maxGearHighLevelRpm === 4000
- ? '当前已解锁超频上限,最高可达 4000 RPM。'
+ ? 'Overclock limit unlocked, max speed up to 4000 RPM.'
: maxGearHighLevelRpm === 3300
- ? '当前最高为强劲档,最高可达 3300 RPM,使用PD 27W充电头以解锁上限。'
+ ? 'Currently at Power gear, max 3300 RPM. Use a PD 27W charger to unlock the limit.'
: maxGearHighLevelRpm === 2760
- ? '当前最高为标准档,最高可达 2760 RPM,使用PD 27W充电头以解锁上限。'
+ ? 'Currently at Standard gear, max 2760 RPM. Use a PD 27W charger to unlock the limit.'
: maxRpmInfo.codeHex
- ? `设备上报了未映射的最高挡位编码:${maxRpmInfo.codeHex}`
- : '等待设备上报最高转速能力。';
+ ? `Device reported an unmapped max gear code: ${maxRpmInfo.codeHex}`
+ : 'Waiting for device to report max speed capability.';
return (
@@ -233,7 +233,7 @@ export default function DeviceStatus({
: 'bg-red-500/10 text-red-500',
)}
>
- {isConnected ? '已连接' : '离线'}
+ {isConnected ? 'Connected' : 'Offline'}
{isConnected && (
@@ -246,7 +246,7 @@ export default function DeviceStatus({
{modeTitle} · {modeDesc}
)}
- {!isConnected && 等待蓝牙连接…
}
+ {!isConnected && Waiting for Bluetooth connection...
}
@@ -255,7 +255,7 @@ export default function DeviceStatus({
@@ -265,7 +265,7 @@ export default function DeviceStatus({
size="md"
onClick={isConnected ? onDisconnect : onConnect}
>
- {isConnected ? '断开' : '连接'}
+ {isConnected ? 'Disconnect' : 'Connect'}
@@ -306,7 +306,7 @@ export default function DeviceStatus({
>
- 风扇
+ Fan
- 设备未连接
- 请将散热器通过蓝牙连接到电脑
+ Device Not Connected
+ Please connect the cooler to your computer via Bluetooth
}>
- 连接设备
+ Connect Device
)}
@@ -343,7 +343,7 @@ export default function DeviceStatus({
-
{temperature?.bridgeMessage || '温度桥接程序读取失败,请检查 PawnIO 驱动后重试。'}
+
{temperature?.bridgeMessage || 'Temperature bridge program failed to read. Please check the PawnIO driver and try again.'}
@@ -360,7 +360,7 @@ export default function DeviceStatus({
- 控制与保护
+ Control & Protection
@@ -368,7 +368,7 @@ export default function DeviceStatus({
- 控制模式
+ Control Mode
{modeDisplayTitle}
@@ -379,7 +379,7 @@ export default function DeviceStatus({
@@ -387,7 +387,7 @@ export default function DeviceStatus({
@@ -406,7 +406,7 @@ export default function DeviceStatus({
- 工作模式
+ Work Mode
{fanData?.workMode || '--'}
@@ -414,7 +414,7 @@ export default function DeviceStatus({
- 温度状态
+ Temp Status
{getTempStatus(temperature?.maxTemp || 0).label}
diff --git a/frontend/src/app/components/FanCurve.tsx b/frontend/src/app/components/FanCurve.tsx
index cdd8408..07b3f93 100644
--- a/frontend/src/app/components/FanCurve.tsx
+++ b/frontend/src/app/components/FanCurve.tsx
@@ -75,7 +75,7 @@ const TemperatureIndicator = memo(function TemperatureIndicator({
- 当前 {temperature}°C
+ Current {temperature}°C
);
});
@@ -88,7 +88,7 @@ const ConfigTooltipLabel = memo(function ConfigTooltipLabel({ label, description
{label}
-
+
@@ -386,14 +386,14 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
try {
setIsSaving(true);
const profileID = activeProfileId || (((config as any).activeFanCurveProfileId || '') as string);
- const profileName = activeProfile?.name || '当前曲线';
+ const profileName = activeProfile?.name || 'Current Curve';
await apiService.saveFanCurveProfile(profileID, profileName, localCurve, true);
await loadCurveProfiles();
await syncConfigFromBackend();
setHasUnsavedChanges(false);
return true;
} catch (e) {
- toast.error(`保存曲线失败: ${e}`);
+ toast.error(`Failed to save curve: ${e}`);
return false;
} finally {
setIsSaving(false);
@@ -437,16 +437,16 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
await apiService.setActiveFanCurveProfile(id);
await loadCurveProfiles();
await syncConfigFromBackend();
- toast.success('温控曲线已切换');
+ toast.success('Fan curve profile switched');
} catch (e) {
- toast.error(`切换失败: ${e}`);
+ toast.error(`Failed to switch: ${e}`);
} finally {
setProfileOpLoading(false);
}
}, [loadCurveProfiles, syncConfigFromBackend]);
const saveCurrentProfileName = useCallback(async () => {
- const fallbackName = activeProfile?.name || '当前曲线';
+ const fallbackName = activeProfile?.name || 'Current Curve';
const safeName = getSafeProfileName(profileNameInput, fallbackName);
try {
setProfileOpLoading(true);
@@ -454,9 +454,9 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
await apiService.saveFanCurveProfile(activeProfileId, safeName, profileCurve, false);
await loadCurveProfiles();
await syncConfigFromBackend();
- toast.success('方案命名已更新');
+ toast.success('Profile name updated');
} catch (e) {
- toast.error(`更新命名失败: ${e}`);
+ toast.error(`Failed to update name: ${e}`);
} finally {
setProfileOpLoading(false);
}
@@ -466,16 +466,16 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
const rawName = (profileNameInput || '').trim();
const activeName = (activeProfile?.name || '').trim();
const shouldUseDefaultNewName = !rawName || rawName === activeName;
- const safeName = shouldUseDefaultNewName ? '新曲线' : getSafeProfileName(rawName, '新曲线');
+ const safeName = shouldUseDefaultNewName ? 'New Curve' : getSafeProfileName(rawName, 'New Curve');
try {
setProfileOpLoading(true);
await apiService.saveFanCurveProfile('', safeName, localCurve, true);
await loadCurveProfiles();
await syncConfigFromBackend();
setProfileNameInput('');
- toast.success('已另存为新方案');
+ toast.success('Saved as new profile');
} catch (e) {
- toast.error(`另存失败: ${e}`);
+ toast.error(`Failed to save as new: ${e}`);
} finally {
setProfileOpLoading(false);
}
@@ -488,9 +488,9 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
await apiService.deleteFanCurveProfile(activeProfileId);
await loadCurveProfiles();
await syncConfigFromBackend();
- toast.success('已删除曲线方案');
+ toast.success('Curve profile deleted');
} catch (e) {
- toast.error(`删除失败: ${e}`);
+ toast.error(`Failed to delete: ${e}`);
} finally {
setProfileOpLoading(false);
}
@@ -509,19 +509,19 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
setExportCode(code);
if (navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(code);
- toast.success('导出字符串已复制');
+ toast.success('Export string copied');
} else {
- toast.success('导出字符串已生成');
+ toast.success('Export string generated');
}
} catch (e) {
- toast.error(`导出失败: ${e}`);
+ toast.error(`Export failed: ${e}`);
}
}, [hasUnsavedChanges, loadCurveProfiles, persistCurrentCurve]);
const importProfiles = useCallback(async () => {
const code = importCode.trim();
if (!code) {
- toast.error('请先粘贴导入字符串');
+ toast.error('Please paste the import string first');
return;
}
try {
@@ -530,9 +530,9 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
await loadCurveProfiles();
await syncConfigFromBackend();
setImportCode('');
- toast.success('曲线方案导入成功');
+ toast.success('Curve profiles imported successfully');
} catch (e) {
- toast.error(`导入失败: ${e}`);
+ toast.error(`Import failed: ${e}`);
} finally {
setProfileOpLoading(false);
}
@@ -630,7 +630,7 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
}, [manualGearPresets]);
const selectedManualPointIndex = useMemo(() => {
- const selected = manualPoints.findIndex((p) => p.gear === (config.manualGear || '标准') && p.level === (config.manualLevel || '中'));
+ const selected = manualPoints.findIndex((p) => p.gear === (config.manualGear || 'Standard') && p.level === (config.manualLevel || 'Mid'));
return selected >= 0 ? selected : 4;
}, [config.manualGear, config.manualLevel, manualPoints]);
@@ -661,9 +661,9 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
const handleGearCardSelect = useCallback(async (gear: string) => {
const rememberedLevel = rememberedManualGearLevels[gear];
- const nextLevel = rememberedLevel === '低' || rememberedLevel === '中' || rememberedLevel === '高'
+ const nextLevel = rememberedLevel === 'Low' || rememberedLevel === 'Mid' || rememberedLevel === 'High'
? rememberedLevel
- : (config.manualLevel || '中');
+ : (config.manualLevel || 'Mid');
await applyManualGearPreset(gear, nextLevel);
}, [applyManualGearPreset, config, rememberedManualGearLevels]);
@@ -690,9 +690,9 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
-
风扇曲线
- {hasUnsavedChanges && 未保存 }
- {isInteracting && 编辑中 }
+ Fan Curve
+ {hasUnsavedChanges && Unsaved }
+ {isInteracting && Editing }
@@ -702,7 +702,7 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
onChange={switchProfile}
loading={profileOpLoading}
/>
-
+
@@ -713,15 +713,15 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
- 手动挡位
- 12 控制点滑块
+ Manual Gear
+ 12 control point slider
{manualGearPresets.map((preset) => {
- const isActiveGear = (config.manualGear || '标准') === preset.gear;
+ const isActiveGear = (config.manualGear || 'Standard') === preset.gear;
const rememberedLevel = isActiveGear
- ? (config.manualLevel || '中')
+ ? (config.manualLevel || 'Mid')
: rememberedManualGearLevels[preset.gear];
const activeLevel = preset.levels.find((l) => l.level === rememberedLevel) ?? preset.levels[1];
return (
@@ -772,7 +772,7 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
{manualPoints.map((point) => (
- {point.levelIndex + 1}档
+ G{point.levelIndex + 1}
))}
@@ -791,11 +791,11 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
-
-
+
+
name === 'coupledRpm' ? [`${value} RPM`, '学习曲线'] : [`${value} RPM`, '基础曲线']}
- labelFormatter={(v) => `温度: ${v}°C`}
+ formatter={(value: number, name: string) => name === 'coupledRpm' ? [`${value} RPM`, 'Learned Curve'] : [`${value} RPM`, 'Base Curve']}
+ labelFormatter={(v) => `Temp: ${v}°C`}
contentStyle={{ backgroundColor: 'var(--chart-tooltip-bg)', border: '1px solid', borderColor: 'var(--chart-tooltip-border)', borderRadius: '8px', boxShadow: 'var(--chart-tooltip-shadow)', padding: '8px 12px', color: 'var(--chart-tooltip-text)' }}
labelStyle={{ color: 'var(--chart-tooltip-text)', fontWeight: 600 }}
itemStyle={{ color: 'var(--chart-tooltip-text)' }}
@@ -811,19 +811,19 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
{/* ── Tips + Actions ── */}
- 拖拽蓝色圆点调整转速
- {showCoupledCurve && 实线: 基础 · 虚线: 学习 }
+ Drag blue dots to adjust speed
+ {showCoupledCurve && Solid: Base · Dashed: Learned }
- }>重置
- }>保存
+ }>Reset
+ }>Save
@@ -851,13 +851,13 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
onChange={(e) => handleProfileNameInputChange(e.target.value, Boolean((e.nativeEvent as InputEvent).isComposing))}
onCompositionStart={handleProfileNameCompositionStart}
onCompositionEnd={(e) => handleProfileNameCompositionEnd(e.currentTarget.value)}
- placeholder="当前方案命名(最多6字)"
+ placeholder="Profile name (max 6 chars)"
className="h-10"
/>
-
}>保存命名
-
}>另存为新方案
-
} disabled={curveProfiles.length <= 1}>删除当前方案
+
}>Save Name
+
}>Save as New
+
} disabled={curveProfiles.length <= 1}>Delete Profile
@@ -867,17 +867,17 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
-
智能学习控温
+
Smart Learning Control
{
if (e && !config.debugMode) {
- toast.error('学习模式暂不稳定,请先开启调试模式后再启用。');
+ toast.error('Learning mode is unstable. Please enable debug mode first.');
return;
}
updateSmartControl({ enabled: config.debugMode && e });
}}
- label="启用"
+ label="Enable"
size="sm"
color="blue"
/>
@@ -896,13 +896,13 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
-
+
{([
- { key: 'quiet', label: '静音' },
- { key: 'balanced', label: '平衡' },
- { key: 'performance', label: '性能' },
+ { key: 'quiet', label: 'Quiet' },
+ { key: 'balanced', label: 'Balanced' },
+ { key: 'performance', label: 'Performance' },
] as const).map((option) => (
-
+
{smartControl.targetTemp}°C
updateSmartControl({ targetTemp: v })} min={55} max={85} step={1} showValue={false} />
-
+
{smartControl.hysteresis}
updateSmartControl({ hysteresis: v })} min={1} max={6} step={1} showValue={false} />
@@ -939,14 +939,14 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
-
+
{smartControl.learnRate}
updateSmartControl({ learnRate: v })} min={1} max={10} step={1} showValue={false} />
-
+
{smartControl.maxLearnOffset} RPM
updateSmartControl({ maxLearnOffset: v })} min={100} max={1200} step={50} showValue={false} />
@@ -954,21 +954,21 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
- 重置学习
+ Reset Learning
{/* Learning visualization */}
- 学习状态
- 温区 {learningInsight.currentTempLabel}
+ Learning Status
+ Temp Zone {learningInsight.currentTempLabel}
{[
- ['当前偏移', `${learningInsight.currentOffset > 0 ? '+' : ''}${learningInsight.currentOffset} RPM`, learningInsight.currentOffset > 0 ? 'text-amber-600' : learningInsight.currentOffset < 0 ? 'text-primary' : ''],
- ['平均强度', `${learningInsight.avgAbsOffset} RPM`, ''],
- ['最大偏移', `${learningInsight.maxAbsOffset} RPM`, ''],
+ ['Current Offset', `${learningInsight.currentOffset > 0 ? '+' : ''}${learningInsight.currentOffset} RPM`, learningInsight.currentOffset > 0 ? 'text-amber-600' : learningInsight.currentOffset < 0 ? 'text-primary' : ''],
+ ['Avg Intensity', `${learningInsight.avgAbsOffset} RPM`, ''],
+ ['Max Offset', `${learningInsight.maxAbsOffset} RPM`, ''],
].map(([label, value, clr]) => (
{label}
@@ -987,7 +987,7 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
))}
) : (
-
暂未形成显著偏移(|偏移| < 20 RPM)。
+
No significant offset formed yet (|offset| < 20 RPM).
)}
@@ -1000,16 +1000,16 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
- 导入 / 导出曲线方案
- 可复制粘贴短字符串
+ Import / Export Curve Profiles
+ Copy and paste short strings
-
导出
+
Export
- }>生成
+ }>Generate
}
disabled={!exportCode}
>
- 复制
+ Copy
@@ -1032,21 +1032,21 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
readOnly
rows={3}
className="w-full rounded-lg border border-border/70 bg-background px-3 py-2 text-xs leading-relaxed"
- placeholder="点击“生成”后显示导出字符串"
+ placeholder="Click 'Generate' to display export string"
/>
- 导入
- }>导入
+ Import
+ }>Import
@@ -1073,14 +1073,14 @@ const FanCurve = memo(function FanCurve({ config, onConfigChange, isConnected, t
- 注意
+ Warning
- 低于1000转非飞智官方设计最低转速标准,由此引发的任何问题需要由用户自行承担!
+ Below 1000 RPM is not the officially designed minimum speed. Any issues arising from this are at the user's own risk!
setShowLowRpmWarning(false)}>
- 我已知悉
+ I Understand
diff --git a/frontend/src/app/components/FanCurveProfileSelect.tsx b/frontend/src/app/components/FanCurveProfileSelect.tsx
index 85417be..7495ef2 100644
--- a/frontend/src/app/components/FanCurveProfileSelect.tsx
+++ b/frontend/src/app/components/FanCurveProfileSelect.tsx
@@ -26,7 +26,7 @@ export default function FanCurveProfileSelect({
onChange,
loading = false,
className,
- placeholder = '选择曲线方案',
+ placeholder = 'Select curve profile',
}: FanCurveProfileSelectProps) {
const options = useMemo(
() => profiles.map((profile) => ({ value: profile.id, label: profile.name })),
@@ -47,7 +47,7 @@ export default function FanCurveProfileSelect({
options={
options.length > 0
? options
- : [{ value: EMPTY_PROFILE_SENTINEL, label: '暂无曲线方案', disabled: true }]
+ : [{ value: EMPTY_PROFILE_SENTINEL, label: 'No curve profiles', disabled: true }]
}
size="sm"
placeholder={placeholder}
diff --git a/frontend/src/app/components/ui/index.tsx b/frontend/src/app/components/ui/index.tsx
index 41034d1..2ea7fd2 100644
--- a/frontend/src/app/components/ui/index.tsx
+++ b/frontend/src/app/components/ui/index.tsx
@@ -95,7 +95,7 @@ export function Select({
onChange,
options,
disabled = false,
- placeholder = '请选择',
+ placeholder = 'Select',
label,
size = 'md',
className,
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 0b36f13..3618b65 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -17,7 +17,7 @@ const geistMono = Geist_Mono({
export const metadata: Metadata = {
title: "BS2PRO Controller",
- description: "BS2PRO 压风控制器桌面端",
+ description: "BS2PRO Fan Controller Desktop App",
};
export default function RootLayout({
diff --git a/frontend/src/app/lib/manualGearPresets.ts b/frontend/src/app/lib/manualGearPresets.ts
index f08034f..41cb0b2 100644
--- a/frontend/src/app/lib/manualGearPresets.ts
+++ b/frontend/src/app/lib/manualGearPresets.ts
@@ -13,47 +13,47 @@ export interface ManualGearPreset {
export const MANUAL_GEAR_PRESETS: ManualGearPreset[] = [
{
- gear: '静音',
+ gear: 'Quiet',
colorClass: 'text-emerald-500',
borderClass: 'border-emerald-500/50',
bgClass: 'bg-emerald-500/12',
levels: [
- { level: '低', rpm: 1300 },
- { level: '中', rpm: 1700 },
- { level: '高', rpm: 1900 },
+ { level: 'Low', rpm: 1300 },
+ { level: 'Mid', rpm: 1700 },
+ { level: 'High', rpm: 1900 },
],
},
{
- gear: '标准',
+ gear: 'Standard',
colorClass: 'text-blue-500',
borderClass: 'border-blue-500/50',
bgClass: 'bg-blue-500/12',
levels: [
- { level: '低', rpm: 2100 },
- { level: '中', rpm: 2310 },
- { level: '高', rpm: 2760 },
+ { level: 'Low', rpm: 2100 },
+ { level: 'Mid', rpm: 2310 },
+ { level: 'High', rpm: 2760 },
],
},
{
- gear: '强劲',
+ gear: 'Power',
colorClass: 'text-purple-500',
borderClass: 'border-purple-500/50',
bgClass: 'bg-purple-500/12',
levels: [
- { level: '低', rpm: 2800 },
- { level: '中', rpm: 3000 },
- { level: '高', rpm: 3300 },
+ { level: 'Low', rpm: 2800 },
+ { level: 'Mid', rpm: 3000 },
+ { level: 'High', rpm: 3300 },
],
},
{
- gear: '超频',
+ gear: 'Overclock',
colorClass: 'text-orange-500',
borderClass: 'border-orange-500/50',
bgClass: 'bg-orange-500/12',
levels: [
- { level: '低', rpm: 3500 },
- { level: '中', rpm: 3700 },
- { level: '高', rpm: 4000 },
+ { level: 'Low', rpm: 3500 },
+ { level: 'Mid', rpm: 3700 },
+ { level: 'High', rpm: 4000 },
],
},
];
@@ -61,7 +61,7 @@ export const MANUAL_GEAR_PRESETS: ManualGearPreset[] = [
export const getManualGearHighLevelRpm = (gear?: string | null): number | undefined => {
if (!gear) return undefined;
const preset = MANUAL_GEAR_PRESETS.find((item) => item.gear === gear);
- return preset?.levels.find((level) => level.level === '高')?.rpm;
+ return preset?.levels.find((level) => level.level === 'High')?.rpm;
};
const MAX_GEAR_CODE_TO_RPM: Record = {
diff --git a/frontend/src/app/services/api.ts b/frontend/src/app/services/api.ts
index 252550f..8648247 100644
--- a/frontend/src/app/services/api.ts
+++ b/frontend/src/app/services/api.ts
@@ -1,4 +1,4 @@
-// Wails API 服务封装
+// Wails API Service Wrapper
import { EventsOn, EventsOff } from '../../../wailsjs/runtime/runtime';
import {
ConnectDevice,
@@ -35,7 +35,7 @@ import type {
} from '../types/app';
class ApiService {
- // 设备连接
+ // Device connection
async connectDevice(): Promise {
return await ConnectDevice();
}
@@ -48,7 +48,7 @@ class ApiService {
return await GetDeviceStatus();
}
- // 配置管理
+ // Configuration management
async getConfig(): Promise {
return await GetConfig();
}
@@ -61,7 +61,7 @@ class ApiService {
return await UpdateConfig(config);
}
- // 风扇曲线
+ // Fan curve
async setFanCurve(curve: types.FanCurvePoint[]): Promise {
return await SetFanCurve(curve);
}
@@ -94,27 +94,27 @@ class ApiService {
return await (window as any).go?.main?.App?.ImportFanCurveProfiles(code);
}
- // 智能变频
+ // Smart speed control
async setAutoControl(enabled: boolean): Promise {
return await SetAutoControl(enabled);
}
- // 自定义转速
+ // Custom speed
async setCustomSpeed(enabled: boolean, rpm: number): Promise {
return await SetCustomSpeed(enabled, rpm);
}
- // 手动挡位控制
+ // Manual gear control
async setManualGear(gear: string, level: string): Promise {
return await SetManualGear(gear, level);
}
- // 获取可用挡位
+ // Get available gears
async getAvailableGears(): Promise {
return await GetAvailableGears();
}
- // 设备设置
+ // Device settings
async setGearLight(enabled: boolean): Promise {
return await SetGearLight(enabled);
}
@@ -135,33 +135,33 @@ class ApiService {
return await SetLightStrip(config);
}
- // Windows自启动相关
+ // Windows auto-start related
async checkWindowsAutoStart(): Promise {
- // 临时使用window对象调用,等Wails生成绑定后更新
+ // Temporarily using window object call, will update after Wails generates bindings
return await (window as any).go?.main?.App?.CheckWindowsAutoStart();
}
async setWindowsAutoStart(enabled: boolean): Promise {
- // 临时使用window对象调用,等Wails生成绑定后更新
+ // Temporarily using window object call, will update after Wails generates bindings
return await (window as any).go?.main?.App?.SetWindowsAutoStart(enabled);
}
async getAutoStartMethod(): Promise {
- // 获取当前自启动方式
+ // Get current auto-start method
return await (window as any).go?.main?.App?.GetAutoStartMethod();
}
async setAutoStartWithMethod(enabled: boolean, method: string): Promise {
- // 使用指定方式设置自启动
+ // Set auto-start with specified method
return await (window as any).go?.main?.App?.SetAutoStartWithMethod(enabled, method);
}
async isRunningAsAdmin(): Promise {
- // 检查是否以管理员权限运行
+ // Check if running as administrator
return await (window as any).go?.main?.App?.IsRunningAsAdmin();
}
- // 数据获取
+ // Data retrieval
async getTemperature(): Promise {
return await GetTemperature();
}
@@ -174,7 +174,7 @@ class ApiService {
return await TestTemperatureReading();
}
- // 桥接程序相关
+ // Bridge program related
async getBridgeProgramStatus(): Promise {
return await (window as any).go?.main?.App?.GetBridgeProgramStatus();
}
@@ -183,7 +183,7 @@ class ApiService {
return await (window as any).go?.main?.App?.TestBridgeProgram();
}
- // 事件监听
+ // Event listeners
onDeviceConnected(callback: (data: DeviceInfo) => void): () => void {
EventsOn('device-connected', callback);
return () => EventsOff('device-connected');
@@ -219,7 +219,7 @@ class ApiService {
return () => EventsOff('hotkey-triggered');
}
- // 调试相关方法
+ // Debug related methods
async getDebugInfo(): Promise {
return await GetDebugInfo();
}
@@ -232,7 +232,7 @@ class ApiService {
return await UpdateGuiResponseTime();
}
- // 调试事件监听
+ // Debug event listeners
onHealthPing(callback: (timestamp: number) => void): () => void {
EventsOn('health-ping', callback);
return () => EventsOff('health-ping');
diff --git a/frontend/src/app/store/app-store.ts b/frontend/src/app/store/app-store.ts
index 707cbc1..32f5ef1 100644
--- a/frontend/src/app/store/app-store.ts
+++ b/frontend/src/app/store/app-store.ts
@@ -4,7 +4,7 @@ import { configService } from '../services/config-service';
import { deviceService, type DeviceStatusPayload } from '../services/device-service';
import { toast } from 'sonner';
-const BRIDGE_WARNING_MESSAGE = 'CPU/GPU 温度读取失败,请检查Pawnio是否安装成功,或升级最新版。';
+const BRIDGE_WARNING_MESSAGE = 'CPU/GPU temperature reading failed. Please check if PawnIO is installed correctly, or upgrade to the latest version.';
type ActiveTab = 'status' | 'curve' | 'control';
@@ -71,8 +71,8 @@ export const useAppStore = create((set, get) => ({
get().handleTemperaturePayload(deviceStatus.temperature || null);
} catch (error) {
- console.error('初始化失败:', error);
- set({ error: '应用初始化失败' });
+ console.error('Initialization failed:', error);
+ set({ error: 'App initialization failed' });
} finally {
set({ isLoading: false });
}
@@ -85,8 +85,8 @@ export const useAppStore = create((set, get) => ({
set({ isConnected: true, error: null });
}
} catch (error) {
- console.error('连接失败:', error);
- set({ error: '设备连接失败' });
+ console.error('Connection failed:', error);
+ set({ error: 'Device connection failed' });
}
},
@@ -95,7 +95,7 @@ export const useAppStore = create((set, get) => ({
await deviceService.disconnect();
set({ isConnected: false, deviceProductId: null, fanData: null });
} catch (error) {
- console.error('断开连接失败:', error);
+ console.error('Disconnect failed:', error);
}
},
@@ -104,8 +104,8 @@ export const useAppStore = create((set, get) => ({
await configService.updateConfig(config);
set({ config, error: null });
} catch (error) {
- console.error('配置更新失败:', error);
- set({ error: '配置保存失败' });
+ console.error('Config update failed:', error);
+ set({ error: 'Config save failed' });
}
},
@@ -114,7 +114,7 @@ export const useAppStore = create((set, get) => ({
unsubscribers.push(
deviceService.onDeviceConnected((deviceInfo) => {
- console.log('设备已连接:', deviceInfo);
+ console.log('Device connected:', deviceInfo);
const info = deviceInfo as { productId?: string };
set({
isConnected: true,
@@ -126,14 +126,14 @@ export const useAppStore = create((set, get) => ({
unsubscribers.push(
deviceService.onDeviceDisconnected(() => {
- console.log('设备已断开');
+ console.log('Device disconnected');
set({ isConnected: false, deviceProductId: null, fanData: null });
})
);
unsubscribers.push(
deviceService.onDeviceError((errorMsg) => {
- console.error('设备错误:', errorMsg);
+ console.error('Device error:', errorMsg);
set({ error: errorMsg });
})
);
@@ -162,9 +162,9 @@ export const useAppStore = create((set, get) => ({
if (!message) return;
const ok = payload?.success !== false;
if (ok) {
- toast.success('功能变动', { description: message, duration: 2600 });
+ toast.success('Feature Changed', { description: message, duration: 2600 });
} else {
- toast.error('操作失败', { description: message, duration: 3200 });
+ toast.error('Operation Failed', { description: message, duration: 3200 });
}
})
);
diff --git a/frontend/src/app/types/app.ts b/frontend/src/app/types/app.ts
index 270805c..183a3ee 100644
--- a/frontend/src/app/types/app.ts
+++ b/frontend/src/app/types/app.ts
@@ -1,9 +1,9 @@
-// 应用类型定义
+// App type definitions
-// 风扇曲线点
+// Fan curve point
export interface FanCurvePoint {
- temperature: number; // 温度 °C
- rpm: number; // 转速 RPM
+ temperature: number; // Temperature °C
+ rpm: number; // Speed RPM
}
export interface FanCurveProfile {
@@ -12,7 +12,7 @@ export interface FanCurveProfile {
curve: FanCurvePoint[];
}
-// 风扇数据结构
+// Fan data structure
export interface FanData {
reportId: number;
magicSync: number;
@@ -28,37 +28,37 @@ export interface FanData {
workMode: string;
}
-// 温度数据
+// Temperature data
export interface TemperatureData {
- cpuTemp: number; // CPU温度
- gpuTemp: number; // GPU温度
- maxTemp: number; // 最高温度
- updateTime: number; // 更新时间戳
- bridgeOk?: boolean; // 桥接程序是否正常
- bridgeMessage?: string; // 桥接程序提示
+ cpuTemp: number; // CPU temperature
+ gpuTemp: number; // GPU temperature
+ maxTemp: number; // Max temperature
+ updateTime: number; // Update timestamp
+ bridgeOk?: boolean; // Bridge program status
+ bridgeMessage?: string; // Bridge program message
}
-// 应用配置
+// App configuration
export interface AppConfig {
- autoControl: boolean; // 智能变频开关
- curveProfileToggleHotkey?: string; // 切换曲线方案快捷键
- fanCurve: FanCurvePoint[]; // 风扇曲线
+ autoControl: boolean; // Smart speed control switch
+ curveProfileToggleHotkey?: string; // Curve profile toggle hotkey
+ fanCurve: FanCurvePoint[]; // Fan curve
fanCurveProfiles?: FanCurveProfile[];
activeFanCurveProfileId?: string;
- gearLight: boolean; // 挡位灯
- powerOnStart: boolean; // 通电自启动
- windowsAutoStart: boolean; // Windows开机自启动
- smartStartStop: string; // 智能启停
- brightness: number; // 亮度
- tempUpdateRate: number; // 温度更新频率(秒)
- configPath: string; // 配置文件路径
- manualGear: string; // 手动挡位设置
- manualLevel: string; // 手动挡位级别(低中高)
- debugMode: boolean; // 调试模式
- guiMonitoring: boolean; // GUI监控开关
- customSpeedEnabled: boolean; // 自定义转速开关
- customSpeedRPM: number; // 自定义转速值(无上下限)
- smartControl: SmartControlConfig; // 学习型智能控温
+ gearLight: boolean; // Gear indicator light
+ powerOnStart: boolean; // Power-on auto start
+ windowsAutoStart: boolean; // Windows boot auto start
+ smartStartStop: string; // Smart start/stop
+ brightness: number; // Brightness
+ tempUpdateRate: number; // Temperature update rate (seconds)
+ configPath: string; // Config file path
+ manualGear: string; // Manual gear setting
+ manualLevel: string; // Manual gear level (low/mid/high)
+ debugMode: boolean; // Debug mode
+ guiMonitoring: boolean; // GUI monitoring switch
+ customSpeedEnabled: boolean; // Custom speed switch
+ customSpeedRPM: number; // Custom speed value (no limits)
+ smartControl: SmartControlConfig; // Learning smart temperature control
}
export interface SmartControlConfig {
@@ -85,7 +85,7 @@ export interface SmartControlConfig {
learnedRateCool: number[];
}
-// 调试信息
+// Debug info
export interface DebugInfo {
debugMode: boolean;
trayReady: boolean;
@@ -96,24 +96,24 @@ export interface DebugInfo {
autoStartLaunch: boolean;
}
-// 自启动方式
+// Auto-start method
export type AutoStartMethod = 'none' | 'task_scheduler' | 'registry';
-// 自启动信息
+// Auto-start info
export interface AutoStartInfo {
enabled: boolean;
method: AutoStartMethod;
isAdmin: boolean;
}
-// 挡位命令
+// Gear command
export interface GearCommand {
- name: string; // 挡位名称
- command: number[]; // 命令字节
- rpm: number; // 对应转速
+ name: string; // Gear name
+ command: number[]; // Command bytes
+ rpm: number; // Corresponding speed
}
-// 设备状态
+// Device status
export interface DeviceStatus {
connected: boolean;
monitoring: boolean;
@@ -123,7 +123,7 @@ export interface DeviceStatus {
model?: string;
}
-// 设备信息
+// Device info
export interface DeviceInfo {
manufacturer: string;
product: string;
diff --git a/internal/autostart/autostart.go b/internal/autostart/autostart.go
index ed2ae28..40254cd 100644
--- a/internal/autostart/autostart.go
+++ b/internal/autostart/autostart.go
@@ -1,4 +1,4 @@
-// Package autostart 提供 Windows 自启动管理功能
+// Package autostart provides Windows auto-start management functionality
package autostart
import (
@@ -14,23 +14,23 @@ import (
"golang.org/x/sys/windows/registry"
)
-// Manager 自启动管理器
+// Manager auto-start manager
type Manager struct {
logger types.Logger
}
-// NewManager 创建新的自启动管理器
+// NewManager creates a new auto-start manager
func NewManager(logger types.Logger) *Manager {
return &Manager{
logger: logger,
}
}
-// IsRunningAsAdmin 检查是否以管理员权限运行
+// IsRunningAsAdmin checks if running with administrator privileges
func (m *Manager) IsRunningAsAdmin() bool {
var sid *windows.SID
- // 创建管理员组的SID
+ // Create administrator group SID
err := windows.AllocateAndInitializeSid(
&windows.SECURITY_NT_AUTHORITY,
2,
@@ -39,47 +39,47 @@ func (m *Manager) IsRunningAsAdmin() bool {
0, 0, 0, 0, 0, 0,
&sid)
if err != nil {
- m.logger.Error("创建管理员SID失败: %v", err)
+ m.logger.Error("Failed to create administrator SID: %v", err)
return false
}
defer windows.FreeSid(sid)
- // 检查当前进程令牌
+ // Check current process token
token := windows.Token(0)
member, err := token.IsMember(sid)
if err != nil {
- m.logger.Error("检查管理员权限失败: %v", err)
+ m.logger.Error("Failed to check administrator privileges: %v", err)
return false
}
return member
}
-// SetWindowsAutoStart 设置Windows开机自启动
+// SetWindowsAutoStart sets Windows startup auto-launch
func (m *Manager) SetWindowsAutoStart(enable bool) error {
- // 检查是否以管理员权限运行
+ // Check if running with administrator privileges
if !m.IsRunningAsAdmin() {
- return fmt.Errorf("设置自启动需要管理员权限")
+ return fmt.Errorf("administrator privileges required to set auto-start")
}
if enable {
- // 使用任务计划程序设置自启动
+ // Use Task Scheduler to set auto-start
return m.createScheduledTask()
} else {
- // 删除任务计划程序和注册表项
+ // Delete Task Scheduler task and registry entry
m.deleteScheduledTask()
return m.removeRegistryAutoStart()
}
}
-// createScheduledTask 创建任务计划程序
+// createScheduledTask creates a scheduled task
func (m *Manager) createScheduledTask() error {
exePath, err := os.Executable()
if err != nil {
- return fmt.Errorf("获取程序路径失败: %v", err)
+ return fmt.Errorf("failed to get executable path: %v", err)
}
- // 获取核心服务路径
+ // Get core service path
exeDir := filepath.Dir(exePath)
corePath := filepath.Join(exeDir, "BS2PRO-Core.exe")
if _, err := os.Stat(corePath); os.IsNotExist(err) {
@@ -98,49 +98,49 @@ func (m *Manager) createScheduledTask() error {
output, err := cmd.CombinedOutput()
if err != nil {
- return fmt.Errorf("创建任务计划失败: %v, 输出: %s", err, string(output))
+ return fmt.Errorf("failed to create scheduled task: %v, output: %s", err, string(output))
}
- m.logger.Info("已通过任务计划程序设置开机自启动")
+ m.logger.Info("Auto-start set via Task Scheduler")
return nil
}
-// deleteScheduledTask 删除任务计划程序
+// deleteScheduledTask deletes the scheduled task
func (m *Manager) deleteScheduledTask() error {
cmd := exec.Command("schtasks", "/delete", "/tn", "BS2PRO-Controller", "/f")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
output, err := cmd.CombinedOutput()
if err != nil {
- if strings.Contains(string(output), "不存在") || strings.Contains(string(output), "cannot be found") {
+ if strings.Contains(string(output), "does not exist") || strings.Contains(string(output), "cannot be found") || strings.Contains(string(output), "不存在") {
return nil
}
- return fmt.Errorf("删除任务计划失败: %v, 输出: %s", err, string(output))
+ return fmt.Errorf("failed to delete scheduled task: %v, output: %s", err, string(output))
}
- m.logger.Info("已删除任务计划程序的自启动任务")
+ m.logger.Info("Deleted auto-start scheduled task")
return nil
}
-// removeRegistryAutoStart 删除注册表自启动项
+// removeRegistryAutoStart removes the registry auto-start entry
func (m *Manager) removeRegistryAutoStart() error {
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
if err != nil {
- return fmt.Errorf("打开注册表失败: %v", err)
+ return fmt.Errorf("failed to open registry: %v", err)
}
defer key.Close()
- // 删除注册表项
+ // Delete registry entry
err = key.DeleteValue("BS2PRO-Controller")
if err != nil && err != registry.ErrNotExist {
- return fmt.Errorf("删除注册表项失败: %v", err)
+ return fmt.Errorf("failed to delete registry entry: %v", err)
}
- m.logger.Info("已删除注册表自启动项")
+ m.logger.Info("Deleted registry auto-start entry")
return nil
}
-// GetAutoStartMethod 获取当前的自启动方式
+// GetAutoStartMethod gets the current auto-start method
func (m *Manager) GetAutoStartMethod() string {
if m.checkScheduledTask() {
return "task_scheduler"
@@ -151,7 +151,7 @@ func (m *Manager) GetAutoStartMethod() string {
return "none"
}
-// SetAutoStartWithMethod 使用指定方式设置自启动
+// SetAutoStartWithMethod sets auto-start using the specified method
func (m *Manager) SetAutoStartWithMethod(enable bool, method string) error {
if !enable {
m.deleteScheduledTask()
@@ -162,7 +162,7 @@ func (m *Manager) SetAutoStartWithMethod(enable bool, method string) error {
switch method {
case "task_scheduler":
if !m.IsRunningAsAdmin() {
- return fmt.Errorf("使用任务计划程序需要管理员权限,请以管理员身份运行程序进行设置")
+ return fmt.Errorf("Task Scheduler requires administrator privileges, please run the program as administrator to configure")
}
return m.createScheduledTask()
@@ -170,26 +170,26 @@ func (m *Manager) SetAutoStartWithMethod(enable bool, method string) error {
return m.setRegistryAutoStart()
default:
- return fmt.Errorf("不支持的自启动方式: %s", method)
+ return fmt.Errorf("unsupported auto-start method: %s", method)
}
}
-// setRegistryAutoStart 设置注册表自启动
+// setRegistryAutoStart sets registry auto-start
func (m *Manager) setRegistryAutoStart() error {
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.SET_VALUE)
if err != nil {
- return fmt.Errorf("打开注册表失败: %v", err)
+ return fmt.Errorf("failed to open registry: %v", err)
}
defer key.Close()
exePath, err := os.Executable()
if err != nil {
- return fmt.Errorf("获取程序路径失败: %v", err)
+ return fmt.Errorf("failed to get executable path: %v", err)
}
exeDir := filepath.Dir(exePath)
corePath := filepath.Join(exeDir, "BS2PRO-Core.exe")
- // 如果核心服务不存在,使用当前程序路径
+ // If core service does not exist, use the current executable path
if _, err := os.Stat(corePath); os.IsNotExist(err) {
corePath = exePath
}
@@ -197,14 +197,14 @@ func (m *Manager) setRegistryAutoStart() error {
err = key.SetStringValue("BS2PRO-Controller", exePathWithArgs)
if err != nil {
- return fmt.Errorf("设置注册表失败: %v", err)
+ return fmt.Errorf("failed to set registry value: %v", err)
}
- m.logger.Info("已通过注册表设置开机自启动")
+ m.logger.Info("Auto-start set via registry")
return nil
}
-// CheckWindowsAutoStart 检查Windows开机自启动状态
+// CheckWindowsAutoStart checks Windows startup auto-launch status
func (m *Manager) CheckWindowsAutoStart() bool {
if m.checkScheduledTask() {
return true
@@ -213,7 +213,7 @@ func (m *Manager) CheckWindowsAutoStart() bool {
return m.checkRegistryAutoStart()
}
-// checkScheduledTask 检查任务计划程序中的自启动任务
+// checkScheduledTask checks if the auto-start task exists in Task Scheduler
func (m *Manager) checkScheduledTask() bool {
cmd := exec.Command("schtasks", "/query", "/tn", "BS2PRO-Controller")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
@@ -222,11 +222,11 @@ func (m *Manager) checkScheduledTask() bool {
return err == nil
}
-// checkRegistryAutoStart 检查注册表中的自启动项
+// checkRegistryAutoStart checks the auto-start entry in the registry
func (m *Manager) checkRegistryAutoStart() bool {
key, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, registry.QUERY_VALUE)
if err != nil {
- m.logger.Debug("打开注册表失败: %v", err)
+ m.logger.Debug("Failed to open registry: %v", err)
return false
}
defer key.Close()
@@ -235,7 +235,7 @@ func (m *Manager) checkRegistryAutoStart() bool {
return err == nil
}
-// DetectAutoStartLaunch 检测是否为自启动启动
+// DetectAutoStartLaunch detects whether the current launch is an auto-start launch
func DetectAutoStartLaunch(args []string) bool {
for _, arg := range args {
if arg == "--autostart" || arg == "/autostart" || arg == "-autostart" {
@@ -247,7 +247,7 @@ func DetectAutoStartLaunch(args []string) bool {
return true
}
- // 检查当前工作目录是否为系统目录
+ // Check if the current working directory is a system directory
wd, err := os.Getwd()
if err == nil {
systemDirs := []string{
@@ -266,9 +266,9 @@ func DetectAutoStartLaunch(args []string) bool {
return false
}
-// isLaunchedByTaskScheduler 检查是否由任务计划程序启动
+// isLaunchedByTaskScheduler checks if launched by Task Scheduler
func isLaunchedByTaskScheduler() bool {
- // 在Windows上检查父进程
+ // Check parent process on Windows
cmd := exec.Command("wmic", "process", "where", fmt.Sprintf("ProcessId=%d", os.Getpid()), "get", "ParentProcessId", "/value")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
@@ -294,7 +294,7 @@ func isLaunchedByTaskScheduler() bool {
return false
}
-// checkParentProcessName 检查父进程名称
+// checkParentProcessName checks the parent process name
func checkParentProcessName(ppid int) bool {
cmd := exec.Command("wmic", "process", "where", fmt.Sprintf("ProcessId=%d", ppid), "get", "Name", "/value")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
@@ -309,7 +309,7 @@ func checkParentProcessName(ppid int) bool {
line = strings.TrimSpace(line)
if after, ok := strings.CutPrefix(line, "Name="); ok {
processName := strings.ToLower(strings.TrimSpace(after))
- // 检查是否为任务计划程序相关进程
+ // Check if it is a Task Scheduler related process
if processName == "taskeng.exe" || processName == "svchost.exe" || processName == "taskhostw.exe" {
return true
}
@@ -319,7 +319,7 @@ func checkParentProcessName(ppid int) bool {
return false
}
-// parseIntSafe 安全解析整数
+// parseIntSafe safely parses an integer
func parseIntSafe(s string) (int, error) {
var result int
_, err := fmt.Sscanf(s, "%d", &result)
diff --git a/internal/bridge/bridge.go b/internal/bridge/bridge.go
index cf347f5..aec3b1b 100644
--- a/internal/bridge/bridge.go
+++ b/internal/bridge/bridge.go
@@ -1,4 +1,4 @@
-// Package bridge 提供温度桥接程序管理功能
+// Package bridge provides temperature bridge program management functionality
package bridge
import (
@@ -18,7 +18,7 @@ import (
"github.com/TIANLI0/BS2PRO-Controller/internal/types"
)
-// Manager 桥接程序管理器
+// Manager bridge program manager
type Manager struct {
cmd *exec.Cmd
conn net.Conn
@@ -33,56 +33,56 @@ const (
bridgePipeName = "BS2PRO_TempBridge"
)
-// NewManager 创建新的桥接程序管理器
+// NewManager creates a new bridge program manager
func NewManager(logger types.Logger) *Manager {
return &Manager{
logger: logger,
}
}
-// EnsureRunning 确保桥接程序正在运行
+// EnsureRunning ensures the bridge program is running
func (m *Manager) EnsureRunning() error {
m.mutex.Lock()
defer m.mutex.Unlock()
- // 已有连接时优先探活;共享桥接场景下 m.cmd 可以为空。
+ // Probe existing connection first; m.cmd can be nil in shared bridge scenarios.
if m.conn != nil {
_, err := m.sendCommandUnsafe("Ping", "")
if err == nil {
- return nil // 连接正常
+ return nil // Connection is healthy
}
- m.logger.Warn("桥接程序连接异常,重新启动: %v", err)
+ m.logger.Warn("Bridge program connection error, restarting: %v", err)
m.stopUnsafe()
}
- // 状态不一致(仅有进程)时进行自愈清理,避免后续阻塞
+ // Self-healing cleanup when state is inconsistent (process only), to avoid blocking
if m.cmd != nil {
- m.logger.Warn("检测到桥接程序状态不一致,执行清理后重启")
+ m.logger.Warn("Inconsistent bridge program state detected, cleaning up and restarting")
m.stopUnsafe()
}
return m.start()
}
-// start 启动桥接程序
+// start starts the bridge program
func (m *Manager) start() error {
if conn, err := m.connectToPipe(bridgePipeName, 500*time.Millisecond); err == nil {
m.conn = conn
m.pipeName = bridgePipeName
m.ownsCmd = false
- m.logger.Info("复用已存在的桥接程序,管道名称: %s", bridgePipeName)
+ m.logger.Info("Reusing existing bridge program, pipe name: %s", bridgePipeName)
return nil
}
exeDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
- return fmt.Errorf("获取程序目录失败: %v", err)
+ return fmt.Errorf("failed to get program directory: %v", err)
}
possiblePaths := []string{
- filepath.Join(exeDir, "bridge", "TempBridge.exe"), // 标准位置: exe同级的bridge目录
- filepath.Join(exeDir, "..", "bridge", "TempBridge.exe"), // 上级目录的bridge目录
- filepath.Join(exeDir, "TempBridge.exe"), // exe同级目录
+ filepath.Join(exeDir, "bridge", "TempBridge.exe"), // Standard location: bridge directory alongside exe
+ filepath.Join(exeDir, "..", "bridge", "TempBridge.exe"), // Bridge directory in parent directory
+ filepath.Join(exeDir, "TempBridge.exe"), // Same directory as exe
}
var bridgePath string
@@ -93,30 +93,30 @@ func (m *Manager) start() error {
}
}
- // 检查桥接程序是否存在
+ // Check if bridge program exists
if bridgePath == "" {
- return fmt.Errorf("TempBridge.exe 不存在,已尝试以下路径: %v", possiblePaths)
+ return fmt.Errorf("TempBridge.exe not found, tried the following paths: %v", possiblePaths)
}
- m.logger.Info("找到桥接程序: %s", bridgePath)
+ m.logger.Info("Found bridge program: %s", bridgePath)
- // 启动桥接程序
+ // Start bridge program
cmd := exec.Command(bridgePath, "--pipe")
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
- // 获取输出管道来读取管道名称
+ // Get output pipe to read pipe name
stdout, err := cmd.StdoutPipe()
if err != nil {
- return fmt.Errorf("创建stdout管道失败: %v", err)
+ return fmt.Errorf("failed to create stdout pipe: %v", err)
}
stderr, err := cmd.StderrPipe()
if err != nil {
- return fmt.Errorf("创建stderr管道失败: %v", err)
+ return fmt.Errorf("failed to create stderr pipe: %v", err)
}
if err := cmd.Start(); err != nil {
- return fmt.Errorf("启动桥接程序失败: %v", err)
+ return fmt.Errorf("failed to start bridge program: %v", err)
}
go func() {
@@ -124,17 +124,17 @@ func (m *Manager) start() error {
for scannerErr.Scan() {
line := strings.TrimSpace(scannerErr.Text())
if line != "" {
- m.logger.Error("桥接程序stderr: %s", line)
+ m.logger.Error("Bridge program stderr: %s", line)
}
}
if err := scannerErr.Err(); err != nil {
- m.logger.Debug("读取桥接程序stderr失败: %v", err)
+ m.logger.Debug("Failed to read bridge program stderr: %v", err)
}
}()
- // 读取管道名称
+ // Read pipe name
scanner := bufio.NewScanner(stdout)
- fmt.Printf("等待桥接程序输出管道名称...\n")
+ fmt.Printf("Waiting for bridge program to output pipe name...\n")
var pipeName string
var attachMode bool
timeout := time.NewTimer(5 * time.Second)
@@ -144,7 +144,7 @@ func (m *Manager) start() error {
go func() {
if scanner.Scan() {
line := scanner.Text()
- fmt.Printf("桥接程序输出: %s\n", line)
+ fmt.Printf("Bridge program output: %s\n", line)
if after, ok := strings.CutPrefix(line, "PIPE:"); ok {
parts := strings.SplitN(after, "|", 2)
pipeName = strings.TrimSpace(parts[0])
@@ -152,7 +152,7 @@ func (m *Manager) start() error {
attachMode = true
}
} else if after0, ok0 := strings.CutPrefix(line, "ERROR:"); ok0 {
- m.logger.Error("桥接程序启动错误: %s", after0)
+ m.logger.Error("Bridge program startup error: %s", after0)
}
}
close(done)
@@ -160,12 +160,12 @@ func (m *Manager) start() error {
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line != "" {
- m.logger.Debug("桥接程序stdout: %s", line)
+ m.logger.Debug("Bridge program stdout: %s", line)
}
}
if err := scanner.Err(); err != nil {
- m.logger.Debug("读取桥接程序stdout失败: %v", err)
+ m.logger.Debug("Failed to read bridge program stdout: %v", err)
}
}()
@@ -173,18 +173,18 @@ func (m *Manager) start() error {
case <-done:
if pipeName == "" {
cmd.Process.Kill()
- return fmt.Errorf("未能获取管道名称")
+ return fmt.Errorf("failed to get pipe name")
}
case <-timeout.C:
cmd.Process.Kill()
- return fmt.Errorf("等待桥接程序启动超时")
+ return fmt.Errorf("timed out waiting for bridge program to start")
}
- // 连接到命名管道
+ // Connect to named pipe
conn, err := m.connectToPipe(pipeName, 5*time.Second)
if err != nil {
cmd.Process.Kill()
- return fmt.Errorf("连接管道失败: %v", err)
+ return fmt.Errorf("failed to connect to pipe: %v", err)
}
m.conn = conn
@@ -194,60 +194,60 @@ func (m *Manager) start() error {
go func() {
_ = cmd.Wait()
}()
- m.logger.Info("桥接程序已存在,附着到共享实例,管道名称: %s", pipeName)
+ m.logger.Info("Bridge program already exists, attaching to shared instance, pipe name: %s", pipeName)
return nil
}
m.cmd = cmd
- m.logger.Info("桥接程序启动成功,管道名称: %s", pipeName)
+ m.logger.Info("Bridge program started successfully, pipe name: %s", pipeName)
return nil
}
-// connectToPipe 连接到命名管道 (使用go-winio实现)
+// connectToPipe connects to a named pipe (using go-winio)
func (m *Manager) connectToPipe(pipeName string, timeout time.Duration) (net.Conn, error) {
pipePath := `\\.\pipe\` + pipeName
deadline := time.Now().Add(timeout)
retryCount := 0
- m.logger.Debug("尝试连接到管道: %s", pipePath)
+ m.logger.Debug("Attempting to connect to pipe: %s", pipePath)
for time.Now().Before(deadline) {
- // 使用go-winio连接命名管道
+ // Connect to named pipe using go-winio
conn, err := winio.DialPipe(pipePath, &timeout)
if err == nil {
- m.logger.Info("成功连接到管道,重试次数: %d", retryCount)
+ m.logger.Info("Successfully connected to pipe, retry count: %d", retryCount)
return conn, nil
}
retryCount++
- if retryCount%50 == 0 { // 每5秒输出一次日志
- m.logger.Debug("连接管道重试中... 第%d次尝试,错误: %v", retryCount, err)
+ if retryCount%50 == 0 { // Log every 5 seconds
+ m.logger.Debug("Retrying pipe connection... attempt %d, error: %v", retryCount, err)
}
time.Sleep(100 * time.Millisecond)
}
- return nil, fmt.Errorf("连接管道超时,总计重试%d次,最后错误可能是权限或管道未就绪", retryCount)
+ return nil, fmt.Errorf("pipe connection timed out, total retries: %d, last error may be permissions or pipe not ready", retryCount)
}
-// SendCommand 发送命令到桥接程序
+// SendCommand sends a command to the bridge program
func (m *Manager) SendCommand(cmdType, data string) (*types.BridgeResponse, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.sendCommandUnsafe(cmdType, data)
}
-// sendCommandUnsafe 发送命令到桥接程序(不加锁版本)
+// sendCommandUnsafe sends a command to the bridge program (lock-free version)
func (m *Manager) sendCommandUnsafe(cmdType, data string) (*types.BridgeResponse, error) {
if m.conn == nil {
- return nil, fmt.Errorf("桥接程序未连接")
+ return nil, fmt.Errorf("bridge program not connected")
}
conn := m.conn
if err := conn.SetDeadline(time.Now().Add(bridgeCommandTimeout)); err != nil {
- m.logger.Debug("设置桥接命令超时失败: %v", err)
+ m.logger.Debug("Failed to set bridge command timeout: %v", err)
}
defer func() {
_ = conn.SetDeadline(time.Time{})
@@ -258,36 +258,36 @@ func (m *Manager) sendCommandUnsafe(cmdType, data string) (*types.BridgeResponse
Data: data,
}
- // 序列化命令
+ // Serialize command
cmdBytes, err := json.Marshal(cmd)
if err != nil {
- return nil, fmt.Errorf("序列化命令失败: %v", err)
+ return nil, fmt.Errorf("failed to serialize command: %v", err)
}
- // 发送命令
+ // Send command
_, err = conn.Write(append(cmdBytes, '\n'))
if err != nil {
m.closeConnUnsafe()
- return nil, fmt.Errorf("发送命令失败: %v", err)
+ return nil, fmt.Errorf("failed to send command: %v", err)
}
reader := bufio.NewReader(conn)
responseBytes, err := reader.ReadBytes('\n')
if err != nil {
m.closeConnUnsafe()
- return nil, fmt.Errorf("读取响应失败: %v", err)
+ return nil, fmt.Errorf("failed to read response: %v", err)
}
var response types.BridgeResponse
err = json.Unmarshal(responseBytes, &response)
if err != nil {
- return nil, fmt.Errorf("解析响应失败: %v", err)
+ return nil, fmt.Errorf("failed to parse response: %v", err)
}
return &response, nil
}
-// closeConnUnsafe 关闭并清理当前连接(不加锁)
+// closeConnUnsafe closes and cleans up the current connection (lock-free)
func (m *Manager) closeConnUnsafe() {
if m.conn != nil {
_ = m.conn.Close()
@@ -295,14 +295,14 @@ func (m *Manager) closeConnUnsafe() {
}
}
-// Stop 停止桥接程序
+// Stop stops the bridge program
func (m *Manager) Stop() {
m.mutex.Lock()
defer m.mutex.Unlock()
m.stopUnsafe()
}
-// stopUnsafe 停止桥接程序(不加锁)
+// stopUnsafe stops the bridge program (lock-free)
func (m *Manager) stopUnsafe() {
ownedCmd := m.cmd
ownsCmd := m.ownsCmd
@@ -312,14 +312,14 @@ func (m *Manager) stopUnsafe() {
if m.conn != nil {
if ownsCmd {
- // 仅关闭当前实例自己启动的桥接进程,避免误杀共享桥接
+ // Only close bridge processes started by the current instance, to avoid killing shared bridges
m.sendCommandUnsafe("Exit", "")
}
m.closeConnUnsafe()
}
if ownsCmd && ownedCmd != nil && ownedCmd.Process != nil {
- // 给程序一些时间来正常退出
+ // Give the program some time to exit gracefully
done := make(chan error, 1)
go func() {
done <- ownedCmd.Wait()
@@ -327,31 +327,31 @@ func (m *Manager) stopUnsafe() {
select {
case <-done:
- // 程序正常退出
+ // Program exited normally
case <-time.After(3 * time.Second):
- // 强制杀死进程
+ // Force kill process
ownedCmd.Process.Kill()
}
}
}
-// GetTemperature 从桥接程序读取温度
+// GetTemperature reads temperature from the bridge program
func (m *Manager) GetTemperature() types.BridgeTemperatureData {
if err := m.EnsureRunning(); err != nil {
return types.BridgeTemperatureData{
Success: false,
- Error: fmt.Sprintf("启动桥接程序失败: %v", err),
+ Error: fmt.Sprintf("failed to start bridge program: %v", err),
}
}
- // 通过管道发送温度请求
+ // Send temperature request via pipe
response, err := m.SendCommand("GetTemperature", "")
if err != nil {
- // 尝试重启桥接程序
+ // Try to restart bridge program
m.Stop()
return types.BridgeTemperatureData{
Success: false,
- Error: fmt.Sprintf("桥接程序通信失败: %v", err),
+ Error: fmt.Sprintf("bridge program communication failed: %v", err),
}
}
@@ -365,20 +365,20 @@ func (m *Manager) GetTemperature() types.BridgeTemperatureData {
if response.Data == nil {
return types.BridgeTemperatureData{
Success: false,
- Error: "桥接程序返回空数据",
+ Error: "bridge program returned empty data",
}
}
return *response.Data
}
-// GetStatus 获取桥接程序状态
+// GetStatus gets the bridge program status
func (m *Manager) GetStatus() map[string]any {
exeDir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
return map[string]any{
"exists": false,
- "error": fmt.Sprintf("获取程序目录失败: %v", err),
+ "error": fmt.Sprintf("failed to get program directory: %v", err),
}
}
@@ -400,7 +400,7 @@ func (m *Manager) GetStatus() map[string]any {
return map[string]any{
"exists": false,
"triedPaths": possiblePaths,
- "error": "TempBridge.exe 不存在",
+ "error": "TempBridge.exe not found",
}
}
diff --git a/internal/config/config.go b/internal/config/config.go
index a5f1d78..d7a258e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -1,4 +1,4 @@
-// Package config 提供配置管理功能
+// Package config provides configuration management functionality
package config
import (
@@ -10,14 +10,14 @@ import (
"github.com/TIANLI0/BS2PRO-Controller/internal/types"
)
-// Manager 配置管理器
+// Manager configuration manager
type Manager struct {
config types.AppConfig
installDir string
logger types.Logger
}
-// NewManager 创建新的配置管理器
+// NewManager creates a new configuration manager
func NewManager(installDir string, logger types.Logger) *Manager {
return &Manager{
installDir: installDir,
@@ -25,59 +25,59 @@ func NewManager(installDir string, logger types.Logger) *Manager {
}
}
-// Load 加载配置
+// Load loads the configuration
func (m *Manager) Load(isAutoStart bool) types.AppConfig {
- // 优先尝试从默认目录加载配置
+ // Try loading config from default directory first
defaultConfigDir := m.GetDefaultConfigDir()
defaultConfigPath := filepath.Join(defaultConfigDir, "config.json")
installConfigPath := filepath.Join(m.installDir, "config", "config.json")
- m.logInfo("尝试从默认目录加载配置: %s", defaultConfigPath)
+ m.logInfo("Trying to load config from default directory: %s", defaultConfigPath)
- // 先尝试从默认目录加载
+ // Try loading from default directory first
if m.tryLoadFromPath(defaultConfigPath) {
m.config.ConfigPath = defaultConfigPath
- m.logInfo("从默认目录加载配置成功: %s", defaultConfigPath)
+ m.logInfo("Config loaded from default directory: %s", defaultConfigPath)
return m.config
}
- m.logInfo("从默认目录加载配置失败,尝试从安装目录加载: %s", installConfigPath)
+ m.logInfo("Failed to load from default directory, trying install directory: %s", installConfigPath)
- // 默认目录失败,尝试从安装目录加载
+ // Default directory failed, try install directory
if m.tryLoadFromPath(installConfigPath) {
m.config.ConfigPath = installConfigPath
- m.logInfo("从安装目录加载配置成功: %s", installConfigPath)
+ m.logInfo("Config loaded from install directory: %s", installConfigPath)
return m.config
}
- m.logError("所有配置目录加载失败,使用默认配置")
+ m.logError("All config directories failed, using default config")
m.config = types.GetDefaultConfig(isAutoStart)
m.config.ConfigPath = defaultConfigPath
if err := m.Save(); err != nil {
- m.logError("保存默认配置失败: %v", err)
+ m.logError("Failed to save default config: %v", err)
}
return m.config
}
-// tryLoadFromPath 尝试从指定路径加载配置
+// tryLoadFromPath tries to load config from the specified path
func (m *Manager) tryLoadFromPath(configPath string) bool {
if _, err := os.Stat(configPath); err != nil {
- m.logDebug("配置文件不存在: %s", configPath)
+ m.logDebug("Config file does not exist: %s", configPath)
return false
}
data, err := os.ReadFile(configPath)
if err != nil {
- m.logError("读取配置文件失败 %s: %v", configPath, err)
+ m.logError("Failed to read config file %s: %v", configPath, err)
return false
}
var config types.AppConfig
if err := json.Unmarshal(data, &config); err != nil {
- m.logError("解析配置文件失败 %s: %v", configPath, err)
+ m.logError("Failed to parse config file %s: %v", configPath, err)
return false
}
@@ -85,27 +85,27 @@ func (m *Manager) tryLoadFromPath(configPath string) bool {
return true
}
-// Save 保存配置
+// Save saves the configuration
func (m *Manager) Save() error {
- // 首先尝试保存到默认目录
+ // Try saving to default directory first
defaultConfigDir := m.GetDefaultConfigDir()
defaultConfigPath := filepath.Join(defaultConfigDir, "config.json")
- m.logDebug("尝试保存配置到默认目录: %s", defaultConfigPath)
+ m.logDebug("Trying to save config to default directory: %s", defaultConfigPath)
- // 确保默认配置目录存在
+ // Ensure default config directory exists
if err := os.MkdirAll(defaultConfigDir, 0755); err != nil {
- m.logError("创建默认配置目录失败: %v", err)
+ m.logError("Failed to create default config directory: %v", err)
} else {
data, err := json.MarshalIndent(m.config, "", " ")
if err != nil {
- m.logError("序列化配置失败: %v", err)
+ m.logError("Failed to serialize config: %v", err)
} else {
if err := os.WriteFile(defaultConfigPath, data, 0644); err != nil {
- m.logError("保存配置到默认目录失败: %v", err)
+ m.logError("Failed to save config to default directory: %v", err)
} else {
m.config.ConfigPath = defaultConfigPath
- m.logInfo("配置保存到默认目录成功: %s", defaultConfigPath)
+ m.logInfo("Config saved to default directory: %s", defaultConfigPath)
return nil
}
}
@@ -114,56 +114,56 @@ func (m *Manager) Save() error {
installConfigDir := filepath.Join(m.installDir, "config")
installConfigPath := filepath.Join(installConfigDir, "config.json")
- m.logInfo("保存到默认目录失败,尝试保存到安装目录: %s", installConfigPath)
+ m.logInfo("Failed to save to default directory, trying install directory: %s", installConfigPath)
if err := os.MkdirAll(installConfigDir, 0755); err != nil {
- m.logError("创建安装配置目录失败: %v", err)
+ m.logError("Failed to create install config directory: %v", err)
return err
}
data, err := json.MarshalIndent(m.config, "", " ")
if err != nil {
- m.logError("序列化配置失败: %v", err)
+ m.logError("Failed to serialize config: %v", err)
return err
}
if err := os.WriteFile(installConfigPath, data, 0644); err != nil {
- m.logError("保存配置到安装目录失败: %v", err)
+ m.logError("Failed to save config to install directory: %v", err)
return err
}
m.config.ConfigPath = installConfigPath
- m.logInfo("配置保存到安装目录成功: %s", installConfigPath)
+ m.logInfo("Config saved to install directory: %s", installConfigPath)
return nil
}
-// GetDefaultConfigDir 获取默认配置目录
+// GetDefaultConfigDir gets the default config directory
func (m *Manager) GetDefaultConfigDir() string {
homeDir, err := os.UserHomeDir()
if err != nil {
- m.logError("获取用户主目录失败: %v", err)
+ m.logError("Failed to get user home directory: %v", err)
return filepath.Join(m.installDir, "config")
}
return filepath.Join(homeDir, ".bs2pro-controller")
}
-// Get 获取当前配置
+// Get gets the current config
func (m *Manager) Get() types.AppConfig {
return m.config
}
-// Set 设置配置
+// Set sets the config
func (m *Manager) Set(config types.AppConfig) {
m.config = config
}
-// Update 更新配置并保存
+// Update updates and saves the config
func (m *Manager) Update(config types.AppConfig) error {
m.config = config
return m.Save()
}
-// 日志辅助方法
+// Log helper methods
func (m *Manager) logInfo(format string, v ...any) {
if m.logger != nil {
m.logger.Info(format, v...)
@@ -182,12 +182,12 @@ func (m *Manager) logDebug(format string, v ...any) {
}
}
-// GetConfigDir 获取配置目录(保持向后兼容)
+// GetConfigDir gets the config directory (backward compatible)
func (m *Manager) GetConfigDir() string {
return m.GetDefaultConfigDir()
}
-// GetInstallDir 获取安装目录
+// GetInstallDir gets the install directory
func GetInstallDir() string {
exePath, err := os.Executable()
if err != nil {
@@ -196,7 +196,7 @@ func GetInstallDir() string {
return filepath.Dir(exePath)
}
-// GetCurrentWorkingDir 获取当前工作目录
+// GetCurrentWorkingDir gets the current working directory
func GetCurrentWorkingDir() string {
dir, err := os.Getwd()
if err != nil {
@@ -205,21 +205,21 @@ func GetCurrentWorkingDir() string {
return dir
}
-// ValidateFanCurve 验证风扇曲线是否有效
+// ValidateFanCurve validates whether a fan curve is valid
func ValidateFanCurve(curve []types.FanCurvePoint) error {
if len(curve) < 2 {
- return fmt.Errorf("风扇曲线至少需要2个点")
+ return fmt.Errorf("fan curve requires at least 2 points")
}
for i := 1; i < len(curve); i++ {
if curve[i].Temperature <= curve[i-1].Temperature {
- return fmt.Errorf("风扇曲线温度点必须递增")
+ return fmt.Errorf("fan curve temperature points must be increasing")
}
}
for i, point := range curve {
if point.RPM < 0 || point.RPM > 4000 {
- return fmt.Errorf("风扇曲线第%d个点RPM超出范围(0-4000)", i+1)
+ return fmt.Errorf("fan curve point %d RPM out of range (0-4000)", i+1)
}
}
diff --git a/internal/device/device.go b/internal/device/device.go
index ec887cb..7358e7b 100644
--- a/internal/device/device.go
+++ b/internal/device/device.go
@@ -1,4 +1,4 @@
-// Package device 提供 HID 设备通信功能
+// Package device provides HID device communication functionality
package device
import (
@@ -13,52 +13,52 @@ import (
)
const (
- // VendorID 设备厂商ID
+ // VendorID device vendor ID
VendorID = 0x37D7
- // ProductID1 产品ID 1 BS2PRO
+ // ProductID1 product ID 1 BS2PRO
ProductID1 = 0x1002
- // ProductID2 产品ID 2 BS2
+ // ProductID2 product ID 2 BS2
ProductID2 = 0x1001
)
-// Manager HID 设备管理器
+// Manager HID device manager
type Manager struct {
device *hid.Device
isConnected bool
- productID uint16 // 当前连接的产品ID
+ productID uint16 // Currently connected product ID
mutex sync.RWMutex
logger types.Logger
currentFanData *types.FanData
- // 回调函数
+ // Callback functions
onFanDataUpdate func(data *types.FanData)
onDisconnect func()
}
-// NewManager 创建新的设备管理器
+// NewManager creates a new device manager
func NewManager(logger types.Logger) *Manager {
return &Manager{
logger: logger,
}
}
-// SetCallbacks 设置回调函数
+// SetCallbacks sets callback functions
func (m *Manager) SetCallbacks(onFanDataUpdate func(data *types.FanData), onDisconnect func()) {
m.onFanDataUpdate = onFanDataUpdate
m.onDisconnect = onDisconnect
}
-// Init 初始化 HID 库
+// Init initializes the HID library
func (m *Manager) Init() error {
return hid.Init()
}
-// Exit 清理 HID 库
+// Exit cleans up the HID library
func (m *Manager) Exit() error {
return hid.Exit()
}
-// Connect 连接 HID 设备
+// Connect connects to the HID device
func (m *Manager) Connect() (bool, map[string]string) {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -73,20 +73,20 @@ func (m *Manager) Connect() (bool, map[string]string) {
var connectedProductID uint16
for _, productID := range productIDs {
- m.logInfo("正在连接设备 - 厂商ID: 0x%04X, 产品ID: 0x%04X", VendorID, productID)
+ m.logInfo("Connecting to device - Vendor ID: 0x%04X, Product ID: 0x%04X", VendorID, productID)
device, err = hid.OpenFirst(VendorID, productID)
if err == nil {
- m.logInfo("成功连接到产品ID: 0x%04X", productID)
+ m.logInfo("Successfully connected to Product ID: 0x%04X", productID)
connectedProductID = productID
break
} else {
- m.logError("产品ID 0x%04X 连接失败: %v", productID, err)
+ m.logError("Product ID 0x%04X connection failed: %v", productID, err)
}
}
if err != nil {
- m.logError("所有设备连接尝试都失败")
+ m.logError("All device connection attempts failed")
return false, nil
}
@@ -99,11 +99,11 @@ func (m *Manager) Connect() (bool, map[string]string) {
modelName = "BS2"
}
- // 获取设备信息
+ // Get device info
deviceInfo, err := device.GetDeviceInfo()
var info map[string]string
if err == nil {
- m.logInfo("设备连接成功: %s %s (型号: %s)", deviceInfo.MfrStr, deviceInfo.ProductStr, modelName)
+ m.logInfo("Device connected: %s %s (Model: %s)", deviceInfo.MfrStr, deviceInfo.ProductStr, modelName)
info = map[string]string{
"manufacturer": deviceInfo.MfrStr,
"product": deviceInfo.ProductStr,
@@ -112,7 +112,7 @@ func (m *Manager) Connect() (bool, map[string]string) {
"productId": fmt.Sprintf("0x%04X", connectedProductID),
}
} else {
- m.logError("设备连接成功,但获取设备信息失败: %v", err)
+ m.logError("Device connected, but failed to get device info: %v", err)
info = map[string]string{
"manufacturer": "Unknown",
"product": modelName,
@@ -122,13 +122,13 @@ func (m *Manager) Connect() (bool, map[string]string) {
}
}
- // 开始监控设备数据
+ // Start monitoring device data
go m.monitorDeviceData()
return true, info
}
-// Disconnect 断开设备连接
+// Disconnect disconnects the device
func (m *Manager) Disconnect() {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -137,35 +137,35 @@ func (m *Manager) Disconnect() {
return
}
- // 关闭设备
+ // Close device
if m.device != nil {
m.device.Close()
m.device = nil
}
m.isConnected = false
- m.logInfo("设备连接已断开")
+ m.logInfo("Device disconnected")
if m.onDisconnect != nil {
m.onDisconnect()
}
}
-// IsConnected 检查设备是否已连接
+// IsConnected checks if the device is connected
func (m *Manager) IsConnected() bool {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.isConnected
}
-// GetProductID 获取当前连接设备的产品ID
+// GetProductID gets the product ID of the currently connected device
func (m *Manager) GetProductID() uint16 {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.productID
}
-// GetModelName 获取当前连接设备的型号名称
+// GetModelName gets the model name of the currently connected device
func (m *Manager) GetModelName() string {
productID := m.GetProductID()
if productID == ProductID2 {
@@ -177,14 +177,14 @@ func (m *Manager) GetModelName() string {
return "Unknown"
}
-// GetCurrentFanData 获取当前风扇数据
+// GetCurrentFanData gets the current fan data
func (m *Manager) GetCurrentFanData() *types.FanData {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.currentFanData
}
-// monitorDeviceData 监控设备数据
+// monitorDeviceData monitors device data
func (m *Manager) monitorDeviceData() {
m.mutex.RLock()
if !m.isConnected || m.device == nil {
@@ -193,10 +193,10 @@ func (m *Manager) monitorDeviceData() {
}
m.mutex.RUnlock()
- // 设置非阻塞模式
+ // Set non-blocking mode
err := m.device.SetNonblock(true)
if err != nil {
- m.logError("设置非阻塞模式失败: %v", err)
+ m.logError("Failed to set non-blocking mode: %v", err)
}
buffer := make([]byte, 64)
@@ -210,34 +210,34 @@ func (m *Manager) monitorDeviceData() {
m.mutex.RUnlock()
if !connected || device == nil {
- m.logInfo("设备已断开,停止数据监控")
+ m.logInfo("Device disconnected, stopping data monitoring")
break
}
n, err := device.ReadWithTimeout(buffer, 1*time.Second)
if err != nil {
if err == hid.ErrTimeout {
- consecutiveErrors = 0 // 超时是正常的,重置错误计数
+ consecutiveErrors = 0 // Timeout is normal, reset error count
continue
}
consecutiveErrors++
- m.logError("读取设备数据失败 (%d/%d): %v", consecutiveErrors, maxConsecutiveErrors, err)
+ m.logError("Failed to read device data (%d/%d): %v", consecutiveErrors, maxConsecutiveErrors, err)
if consecutiveErrors >= maxConsecutiveErrors {
- m.logError("连续读取失败次数过多,设备可能已断开")
+ m.logError("Too many consecutive read failures, device may be disconnected")
break
}
- // 短暂等待后重试
+ // Brief wait before retry
time.Sleep(500 * time.Millisecond)
continue
}
- consecutiveErrors = 0 // 成功读取,重置错误计数
+ consecutiveErrors = 0 // Successful read, reset error count
if n > 0 {
- // 解析风扇数据
+ // Parse fan data
fanData := m.parseFanData(buffer, n)
if fanData != nil {
m.mutex.Lock()
@@ -250,15 +250,15 @@ func (m *Manager) monitorDeviceData() {
}
}
- // 短暂休眠,避免高CPU占用
+ // Brief sleep to avoid high CPU usage
time.Sleep(100 * time.Millisecond)
}
- // 设备监控循环退出,触发断开处理
+ // Device monitoring loop exited, trigger disconnect handling
m.handleDeviceDisconnected()
}
-// handleDeviceDisconnected 处理设备断开
+// handleDeviceDisconnected handles device disconnection
func (m *Manager) handleDeviceDisconnected() {
m.mutex.Lock()
wasConnected := m.isConnected
@@ -267,7 +267,7 @@ func (m *Manager) handleDeviceDisconnected() {
func() {
defer func() {
if r := recover(); r != nil {
- m.logError("关闭设备时发生错误: %v", r)
+ m.logError("Error occurred while closing device: %v", r)
}
}()
m.device.Close()
@@ -279,20 +279,20 @@ func (m *Manager) handleDeviceDisconnected() {
m.mutex.Unlock()
if wasConnected {
- m.logInfo("设备连接已断开")
+ m.logInfo("Device disconnected")
if m.onDisconnect != nil {
m.onDisconnect()
}
}
}
-// parseFanData 解析风扇数据
+// parseFanData parses fan data
func (m *Manager) parseFanData(data []byte, length int) *types.FanData {
if length < 11 {
return nil
}
- // 检查同步头
+ // Check sync header
magic := binary.BigEndian.Uint16(data[1:3])
if magic != 0x5AA5 {
return nil
@@ -312,7 +312,7 @@ func (m *Manager) parseFanData(data []byte, length int) *types.FanData {
Reserved1: data[7],
}
- // 解析转速 (小端序)
+ // Parse RPM (little-endian)
if length >= 10 {
fanData.CurrentRPM = binary.LittleEndian.Uint16(data[8:10])
}
@@ -320,7 +320,7 @@ func (m *Manager) parseFanData(data []byte, length int) *types.FanData {
fanData.TargetRPM = binary.LittleEndian.Uint16(data[10:12])
}
- // 解析挡位设置
+ // Parse gear settings
maxGear, setGear := m.parseGearSettings(fanData.GearSettings)
fanData.MaxGear = maxGear
fanData.SetGear = setGear
@@ -330,52 +330,52 @@ func (m *Manager) parseFanData(data []byte, length int) *types.FanData {
return fanData
}
-// parseGearSettings 解析挡位设置
+// parseGearSettings parses gear settings
func (m *Manager) parseGearSettings(gearByte uint8) (maxGear, setGear string) {
maxGearCode := (gearByte >> 4) & 0x0F
setGearCode := gearByte & 0x0F
maxGearMap := map[uint8]string{
- 0x2: "标准",
- 0x4: "强劲",
- 0x6: "超频",
+ 0x2: "Standard",
+ 0x4: "Power",
+ 0x6: "Overclock",
}
setGearMap := map[uint8]string{
- 0x8: "静音",
- 0xA: "标准",
- 0xC: "强劲",
- 0xE: "超频",
+ 0x8: "Silent",
+ 0xA: "Standard",
+ 0xC: "Power",
+ 0xE: "Overclock",
}
if val, ok := maxGearMap[maxGearCode]; ok {
maxGear = val
} else {
- maxGear = fmt.Sprintf("未知(0x%X)", maxGearCode)
+ maxGear = fmt.Sprintf("Unknown(0x%X)", maxGearCode)
}
if val, ok := setGearMap[setGearCode]; ok {
setGear = val
} else {
- setGear = fmt.Sprintf("未知(0x%X)", setGearCode)
+ setGear = fmt.Sprintf("Unknown(0x%X)", setGearCode)
}
return
}
-// parseWorkMode 解析工作模式
+// parseWorkMode parses the work mode
func (m *Manager) parseWorkMode(mode uint8) string {
switch mode {
case 0x04, 0x02, 0x06, 0x0A, 0x08, 0x00:
- return "挡位工作模式"
+ return "Gear Mode"
case 0x05, 0x03, 0x07, 0x0B, 0x09, 0x01:
- return "自动模式(实时转速)"
+ return "Auto Mode (Real-time RPM)"
default:
- return fmt.Sprintf("未知模式(0x%02X)", mode)
+ return fmt.Sprintf("Unknown Mode(0x%02X)", mode)
}
}
-// SetFanSpeed 设置风扇转速
+// SetFanSpeed sets the fan speed
func (m *Manager) SetFanSpeed(rpm int) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -388,43 +388,43 @@ func (m *Manager) SetFanSpeed(rpm int) bool {
return false
}
- // 首先进入实时转速模式
+ // First enter real-time RPM mode
enterModeCmd := []byte{0x02, 0x5A, 0xA5, 0x23, 0x02, 0x25, 0x00}
- // 补齐到23字节
+ // Pad to 23 bytes
enterModeCmd = append(enterModeCmd, make([]byte, 23-len(enterModeCmd))...)
_, err := m.device.Write(enterModeCmd)
if err != nil {
- m.logError("进入实时转速模式失败: %v", err)
+ m.logError("Failed to enter real-time RPM mode: %v", err)
return false
}
time.Sleep(50 * time.Millisecond)
- // 构造转速设置命令
+ // Construct RPM setting command
speedBytes := make([]byte, 2)
binary.LittleEndian.PutUint16(speedBytes, uint16(rpm))
- // 计算校验和
+ // Calculate checksum
checksum := (0x5A + 0xA5 + 0x21 + 0x04 + int(speedBytes[0]) + int(speedBytes[1]) + 1) & 0xFF
cmd := []byte{0x02, 0x5A, 0xA5, 0x21, 0x04}
cmd = append(cmd, speedBytes...)
cmd = append(cmd, byte(checksum))
- // 补齐到23字节
+ // Pad to 23 bytes
cmd = append(cmd, make([]byte, 23-len(cmd))...)
_, err = m.device.Write(cmd)
if err != nil {
- m.logError("设置风扇转速失败: %v", err)
+ m.logError("Failed to set fan speed: %v", err)
return false
}
- m.logDebug("已设置风扇转速: %d RPM", rpm)
+ m.logDebug("Fan speed set to: %d RPM", rpm)
return true
}
-// SetCustomFanSpeed 设置自定义风扇转速(无限制)
+// SetCustomFanSpeed sets custom fan speed (no limits)
func (m *Manager) SetCustomFanSpeed(rpm int) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -433,14 +433,14 @@ func (m *Manager) SetCustomFanSpeed(rpm int) bool {
return false
}
- m.logWarn("警告:设置自定义转速 %d RPM(无上下限限制)", rpm)
+ m.logWarn("Warning: setting custom speed %d RPM (no upper/lower limits)", rpm)
enterModeCmd := []byte{0x02, 0x5A, 0xA5, 0x23, 0x02, 0x25, 0x00}
enterModeCmd = append(enterModeCmd, make([]byte, 23-len(enterModeCmd))...)
_, err := m.device.Write(enterModeCmd)
if err != nil {
- m.logError("进入实时转速模式失败: %v", err)
+ m.logError("Failed to enter real-time RPM mode: %v", err)
return false
}
@@ -449,7 +449,7 @@ func (m *Manager) SetCustomFanSpeed(rpm int) bool {
speedBytes := make([]byte, 2)
binary.LittleEndian.PutUint16(speedBytes, uint16(rpm))
- // 计算校验和
+ // Calculate checksum
checksum := (0x5A + 0xA5 + 0x21 + 0x04 + int(speedBytes[0]) + int(speedBytes[1]) + 1) & 0xFF
cmd := []byte{0x02, 0x5A, 0xA5, 0x21, 0x04}
@@ -459,38 +459,38 @@ func (m *Manager) SetCustomFanSpeed(rpm int) bool {
_, err = m.device.Write(cmd)
if err != nil {
- m.logError("设置自定义风扇转速失败: %v", err)
+ m.logError("Failed to set custom fan speed: %v", err)
return false
}
- m.logInfo("已设置自定义风扇转速: %d RPM", rpm)
+ m.logInfo("Custom fan speed set to: %d RPM", rpm)
return true
}
-// EnterAutoMode 进入自动模式
+// EnterAutoMode enters auto mode
func (m *Manager) EnterAutoMode() error {
m.mutex.Lock()
defer m.mutex.Unlock()
if !m.isConnected || m.device == nil {
- return fmt.Errorf("设备未连接")
+ return fmt.Errorf("device not connected")
}
- // 发送进入实时转速模式的命令
+ // Send command to enter real-time RPM mode
enterModeCmd := []byte{0x02, 0x5A, 0xA5, 0x23, 0x02, 0x25, 0x00}
- // 补齐到23字节
+ // Pad to 23 bytes
enterModeCmd = append(enterModeCmd, make([]byte, 23-len(enterModeCmd))...)
_, err := m.device.Write(enterModeCmd)
if err != nil {
- return fmt.Errorf("进入自动模式失败: %v", err)
+ return fmt.Errorf("failed to enter auto mode: %v", err)
}
- m.logInfo("已切换到自动模式,开始智能变频")
+ m.logInfo("Switched to auto mode, starting smart fan control")
return nil
}
-// SetManualGear 设置手动挡位
+// SetManualGear sets the manual gear
func (m *Manager) SetManualGear(gear, level string) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -501,7 +501,7 @@ func (m *Manager) SetManualGear(gear, level string) bool {
commands, exists := types.GearCommands[gear]
if !exists {
- m.logError("未找到挡位 %s 的命令", gear)
+ m.logError("Command not found for gear %s", gear)
return false
}
@@ -509,16 +509,16 @@ func (m *Manager) SetManualGear(gear, level string) bool {
for i := range commands {
cmd := &commands[i]
switch level {
- case "低":
- if strings.Contains(cmd.Name, "低") {
+ case "低", "low":
+ if strings.Contains(cmd.Name, "低") || strings.Contains(cmd.Name, "low") {
selectedCommand = cmd
}
- case "中":
- if strings.Contains(cmd.Name, "中") {
+ case "中", "medium":
+ if strings.Contains(cmd.Name, "中") || strings.Contains(cmd.Name, "medium") {
selectedCommand = cmd
}
- case "高":
- if strings.Contains(cmd.Name, "高") {
+ case "高", "high":
+ if strings.Contains(cmd.Name, "高") || strings.Contains(cmd.Name, "high") {
selectedCommand = cmd
}
}
@@ -528,24 +528,24 @@ func (m *Manager) SetManualGear(gear, level string) bool {
}
if selectedCommand == nil {
- m.logError("未找到挡位 %s %s 的命令", gear, level)
+ m.logError("Command not found for gear %s level %s", gear, level)
return false
}
- // 发送命令,确保第一个字节是ReportID
+ // Send command, ensure first byte is ReportID
cmdWithReportID := append([]byte{0x02}, selectedCommand.Command...)
_, err := m.device.Write(cmdWithReportID)
if err != nil {
- m.logError("设置挡位 %s %s 失败: %v", gear, level, err)
+ m.logError("Failed to set gear %s %s: %v", gear, level, err)
return false
}
- m.logInfo("设置挡位成功: %s %s (目标转速: %d RPM)", gear, level, selectedCommand.RPM)
+ m.logInfo("Gear set successfully: %s %s (target RPM: %d)", gear, level, selectedCommand.RPM)
return true
}
-// SetGearLight 设置挡位灯
+// SetGearLight sets the gear indicator light
func (m *Manager) SetGearLight(enabled bool) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -561,19 +561,19 @@ func (m *Manager) SetGearLight(enabled bool) bool {
cmd = []byte{0x02, 0x5A, 0xA5, 0x48, 0x03, 0x00, 0x4B}
}
- // 补齐到23字节
+ // Pad to 23 bytes
cmd = append(cmd, make([]byte, 23-len(cmd))...)
_, err := m.device.Write(cmd)
if err != nil {
- m.logError("设置挡位灯失败: %v", err)
+ m.logError("Failed to set gear light: %v", err)
return false
}
return true
}
-// SetPowerOnStart 设置通电自启动
+// SetPowerOnStart sets power-on auto-start
func (m *Manager) SetPowerOnStart(enabled bool) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -589,19 +589,19 @@ func (m *Manager) SetPowerOnStart(enabled bool) bool {
cmd = []byte{0x02, 0x5A, 0xA5, 0x0C, 0x03, 0x01, 0x10}
}
- // 补齐到23字节
+ // Pad to 23 bytes
cmd = append(cmd, make([]byte, 23-len(cmd))...)
_, err := m.device.Write(cmd)
if err != nil {
- m.logError("设置通电自启动失败: %v", err)
+ m.logError("Failed to set power-on auto-start: %v", err)
return false
}
return true
}
-// SetSmartStartStop 设置智能启停
+// SetSmartStartStop sets smart start/stop
func (m *Manager) SetSmartStartStop(mode string) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -622,19 +622,19 @@ func (m *Manager) SetSmartStartStop(mode string) bool {
return false
}
- // 补齐到23字节
+ // Pad to 23 bytes
cmd = append(cmd, make([]byte, 23-len(cmd))...)
_, err := m.device.Write(cmd)
if err != nil {
- m.logError("设置智能启停失败: %v", err)
+ m.logError("Failed to set smart start/stop: %v", err)
return false
}
return true
}
-// SetBrightness 设置亮度
+// SetBrightness sets the brightness
func (m *Manager) SetBrightness(percentage int) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -651,11 +651,11 @@ func (m *Manager) SetBrightness(percentage int) bool {
switch percentage {
case 0:
cmd = []byte{0x02, 0x5A, 0xA5, 0x47, 0x0D, 0x1C, 0x00, 0xFF}
- // 补齐到23字节
+ // Pad to 23 bytes
cmd = append(cmd, make([]byte, 23-len(cmd))...)
case 100:
cmd = []byte{0x02, 0x5A, 0xA5, 0x43, 0x02, 0x45}
- // 补齐到23字节
+ // Pad to 23 bytes
cmd = append(cmd, make([]byte, 23-len(cmd))...)
default:
return false
@@ -663,14 +663,14 @@ func (m *Manager) SetBrightness(percentage int) bool {
_, err := m.device.Write(cmd)
if err != nil {
- m.logError("设置亮度失败: %v", err)
+ m.logError("Failed to set brightness: %v", err)
return false
}
return true
}
-// 日志辅助方法
+// Log helper methods
func (m *Manager) logInfo(format string, v ...any) {
if m.logger != nil {
m.logger.Info(format, v...)
diff --git a/internal/device/rgb.go b/internal/device/rgb.go
index 2dea329..8274ca6 100644
--- a/internal/device/rgb.go
+++ b/internal/device/rgb.go
@@ -13,13 +13,13 @@ const (
lightSpeedSlow byte = 0x0F
)
-// SetLightStrip 设置灯带模式
+// SetLightStrip sets the light strip mode
func (m *Manager) SetLightStrip(cfg types.LightStripConfig) error {
m.mutex.Lock()
defer m.mutex.Unlock()
if !m.isConnected || m.device == nil {
- return fmt.Errorf("设备未连接")
+ return fmt.Errorf("device not connected")
}
brightness := clampLightBrightness(cfg.Brightness)
@@ -45,11 +45,11 @@ func (m *Manager) SetLightStrip(cfg types.LightStripConfig) error {
colors := ensureMinColors(cfg.Colors, 1)
return m.setLightBreathingLocked(colors, speed, brightness)
default:
- return fmt.Errorf("未知灯带模式: %s", cfg.Mode)
+ return fmt.Errorf("unknown light strip mode: %s", cfg.Mode)
}
}
-// SetRGBOff 关闭RGB灯光
+// SetRGBOff turns off RGB lights
func (m *Manager) SetRGBOff() bool {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -213,7 +213,7 @@ func (m *Manager) setLightStaticMultiLocked(colors [3]types.RGBColor, brightness
func (m *Manager) setLightRotationLocked(colors []types.RGBColor, speed, brightness byte) error {
if len(colors) < 1 {
- return fmt.Errorf("旋转需要至少 1 个颜色")
+ return fmt.Errorf("rotation requires at least 1 color")
}
if len(colors) > 6 {
colors = colors[:6]
@@ -289,7 +289,7 @@ func (m *Manager) setLightFlowingLocked(speed, brightness byte) error {
func (m *Manager) setLightBreathingLocked(colors []types.RGBColor, speed, brightness byte) error {
if len(colors) == 0 {
- return fmt.Errorf("颜色列表不能为空")
+ return fmt.Errorf("color list cannot be empty")
}
if len(colors) > 5 {
colors = colors[:5]
diff --git a/internal/hotkey/manager.go b/internal/hotkey/manager.go
index 296d18c..bf0545a 100644
--- a/internal/hotkey/manager.go
+++ b/internal/hotkey/manager.go
@@ -10,7 +10,7 @@ import (
hotkeylib "golang.design/x/hotkey"
)
-// Action 定义快捷键触发的动作。
+// Action defines the action triggered by a hotkey.
type Action string
const (
@@ -19,7 +19,7 @@ const (
ActionToggleCurveProfile Action = "toggle-curve-profile"
)
-// Manager 负责注册/更新/注销全局快捷键。
+// Manager is responsible for registering/updating/unregistering global hotkeys.
type Manager struct {
logger types.Logger
onAction func(action Action, shortcut string)
@@ -35,7 +35,7 @@ type binding struct {
stopChan chan struct{}
}
-// NewManager 创建快捷键管理器。
+// NewManager creates a hotkey manager.
func NewManager(logger types.Logger, onAction func(action Action, shortcut string)) *Manager {
return &Manager{
logger: logger,
@@ -44,7 +44,7 @@ func NewManager(logger types.Logger, onAction func(action Action, shortcut strin
}
}
-// UpdateBindings 根据配置更新快捷键绑定。
+// UpdateBindings updates hotkey bindings based on configuration.
func (m *Manager) UpdateBindings(manualGearShortcut, autoControlShortcut, curveProfileShortcut string) error {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -70,7 +70,7 @@ func (m *Manager) UpdateBindings(manualGearShortcut, autoControlShortcut, curveP
return nil
}
-// Stop 释放所有快捷键资源。
+// Stop releases all hotkey resources.
func (m *Manager) Stop() {
m.mutex.Lock()
defer m.mutex.Unlock()
@@ -101,12 +101,12 @@ func (m *Manager) setBinding(action Action, shortcut string) error {
mods, key, err := ParseShortcut(shortcut)
if err != nil {
- return fmt.Errorf("%s 快捷键无效: %w", action, err)
+ return fmt.Errorf("%s invalid shortcut: %w", action, err)
}
hk := hotkeylib.New(mods, key)
if err := hk.Register(); err != nil {
- return fmt.Errorf("%s 注册失败(%s): %w", action, shortcut, err)
+ return fmt.Errorf("%s registration failed (%s): %w", action, shortcut, err)
}
b := &binding{
@@ -117,7 +117,7 @@ func (m *Manager) setBinding(action Action, shortcut string) error {
m.bindings[action] = b
go m.listen(action, b)
- m.logInfo("快捷键已注册: %s -> %s", action, shortcut)
+ m.logInfo("Hotkey registered: %s -> %s", action, shortcut)
return nil
}
@@ -128,7 +128,7 @@ func (m *Manager) unregisterBinding(action Action) {
}
close(b.stopChan)
if err := b.hk.Unregister(); err != nil {
- m.logDebug("快捷键注销失败: %s (%v)", action, err)
+ m.logDebug("Hotkey unregister failed: %s (%v)", action, err)
}
delete(m.bindings, action)
}
@@ -146,7 +146,7 @@ func (m *Manager) listen(action Action, b *binding) {
}
}
-// ParseShortcut 解析快捷键字符串,支持 Ctrl/Alt/Shift/Win + A-Z/0-9/F1-F12。
+// ParseShortcut parses a shortcut string, supporting Ctrl/Alt/Shift/Win + A-Z/0-9/F1-F12.
func ParseShortcut(input string) ([]hotkeylib.Modifier, hotkeylib.Key, error) {
normalized := normalizeShortcut(input)
if normalized == "" {
diff --git a/internal/ipc/ipc.go b/internal/ipc/ipc.go
index 7841715..1cf307a 100644
--- a/internal/ipc/ipc.go
+++ b/internal/ipc/ipc.go
@@ -1,4 +1,4 @@
-// Package ipc 提供核心服务与 GUI 之间的进程间通信
+// Package ipc provides inter-process communication between core service and GUI
package ipc
import (
@@ -15,23 +15,23 @@ import (
)
const (
- // PipeName 命名管道名称
+ // PipeName named pipe name
PipeName = "BS2PRO-Controller-IPC"
- // PipePath 命名管道完整路径
+ // PipePath named pipe full path
PipePath = `\\.\pipe\` + PipeName
)
-// RequestType 请求类型
+// RequestType request type
type RequestType string
const (
- // 设备相关
+ // Device related
ReqConnect RequestType = "Connect"
ReqDisconnect RequestType = "Disconnect"
ReqGetDeviceStatus RequestType = "GetDeviceStatus"
ReqGetCurrentFanData RequestType = "GetCurrentFanData"
- // 配置相关
+ // Config related
ReqGetConfig RequestType = "GetConfig"
ReqUpdateConfig RequestType = "UpdateConfig"
ReqSetFanCurve RequestType = "SetFanCurve"
@@ -43,7 +43,7 @@ const (
ReqExportFanCurveProfiles RequestType = "ExportFanCurveProfiles"
ReqImportFanCurveProfiles RequestType = "ImportFanCurveProfiles"
- // 控制相关
+ // Control related
ReqSetAutoControl RequestType = "SetAutoControl"
ReqSetManualGear RequestType = "SetManualGear"
ReqGetAvailableGears RequestType = "GetAvailableGears"
@@ -54,58 +54,58 @@ const (
ReqSetBrightness RequestType = "SetBrightness"
ReqSetLightStrip RequestType = "SetLightStrip"
- // 温度相关
+ // Temperature related
ReqGetTemperature RequestType = "GetTemperature"
ReqTestTemperatureReading RequestType = "TestTemperatureReading"
ReqTestBridgeProgram RequestType = "TestBridgeProgram"
ReqGetBridgeProgramStatus RequestType = "GetBridgeProgramStatus"
- // 自启动相关
+ // Auto-start related
ReqSetWindowsAutoStart RequestType = "SetWindowsAutoStart"
ReqCheckWindowsAutoStart RequestType = "CheckWindowsAutoStart"
ReqIsRunningAsAdmin RequestType = "IsRunningAsAdmin"
ReqGetAutoStartMethod RequestType = "GetAutoStartMethod"
ReqSetAutoStartWithMethod RequestType = "SetAutoStartWithMethod"
- // 窗口相关
+ // Window related
ReqShowWindow RequestType = "ShowWindow"
ReqHideWindow RequestType = "HideWindow"
ReqQuitApp RequestType = "QuitApp"
- // 调试相关
+ // Debug related
ReqGetDebugInfo RequestType = "GetDebugInfo"
ReqSetDebugMode RequestType = "SetDebugMode"
ReqUpdateGuiResponseTime RequestType = "UpdateGuiResponseTime"
- // 系统相关
+ // System related
ReqPing RequestType = "Ping"
ReqIsAutoStartLaunch RequestType = "IsAutoStartLaunch"
ReqSubscribeEvents RequestType = "SubscribeEvents"
ReqUnsubscribeEvents RequestType = "UnsubscribeEvents"
)
-// Request IPC 请求
+// Request IPC request
type Request struct {
Type RequestType `json:"type"`
Data json.RawMessage `json:"data,omitempty"`
}
-// Response IPC 响应
+// Response IPC response
type Response struct {
- IsResponse bool `json:"isResponse"` // 标识这是响应而非事件
+ IsResponse bool `json:"isResponse"` // Identifies this as a response, not an event
Success bool `json:"success"`
Error string `json:"error,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
-// Event IPC 事件(服务器推送给客户端)
+// Event IPC event (pushed from server to client)
type Event struct {
- IsEvent bool `json:"isEvent"` // 标识这是事件
+ IsEvent bool `json:"isEvent"` // Identifies this as an event
Type string `json:"type"`
Data json.RawMessage `json:"data,omitempty"`
}
-// EventType 事件类型
+// EventType event types
const (
EventFanDataUpdate = "fan-data-update"
EventTemperatureUpdate = "temperature-update"
@@ -118,7 +118,7 @@ const (
EventHeartbeat = "heartbeat"
)
-// Server IPC 服务器
+// Server IPC server
type Server struct {
listener net.Listener
clients map[net.Conn]bool
@@ -128,10 +128,10 @@ type Server struct {
running bool
}
-// RequestHandler 请求处理函数类型
+// RequestHandler request handler function type
type RequestHandler func(req Request) Response
-// NewServer 创建 IPC 服务器
+// NewServer creates an IPC server
func NewServer(handler RequestHandler, logger types.Logger) *Server {
return &Server{
clients: make(map[net.Conn]bool),
@@ -140,35 +140,35 @@ func NewServer(handler RequestHandler, logger types.Logger) *Server {
}
}
-// Start 启动服务器
+// Start starts the server
func (s *Server) Start() error {
- // 创建命名管道监听器
+ // Create named pipe listener
cfg := &winio.PipeConfig{
- SecurityDescriptor: "D:P(A;;GA;;;WD)", // 允许所有用户访问
+ SecurityDescriptor: "D:P(A;;GA;;;WD)", // Allow all users access
}
listener, err := winio.ListenPipe(PipePath, cfg)
if err != nil {
- return fmt.Errorf("创建命名管道失败: %v", err)
+ return fmt.Errorf("failed to create named pipe: %v", err)
}
s.listener = listener
s.running = true
- s.logInfo("IPC 服务器已启动: %s", PipePath)
+ s.logInfo("IPC server started: %s", PipePath)
- // 接受连接
+ // Accept connections
go s.acceptConnections()
return nil
}
-// acceptConnections 接受客户端连接
+// acceptConnections accepts client connections
func (s *Server) acceptConnections() {
for s.running {
conn, err := s.listener.Accept()
if err != nil {
if s.running {
- s.logError("接受连接失败: %v", err)
+ s.logError("Failed to accept connection: %v", err)
}
continue
}
@@ -177,19 +177,19 @@ func (s *Server) acceptConnections() {
s.clients[conn] = true
s.mutex.Unlock()
- s.logInfo("新的 IPC 客户端已连接")
+ s.logInfo("New IPC client connected")
go s.handleClient(conn)
}
}
-// handleClient 处理客户端连接
+// handleClient handles a client connection
func (s *Server) handleClient(conn net.Conn) {
defer func() {
s.mutex.Lock()
delete(s.clients, conn)
s.mutex.Unlock()
conn.Close()
- s.logInfo("IPC 客户端已断开")
+ s.logInfo("IPC client disconnected")
}()
reader := bufio.NewReader(conn)
@@ -197,51 +197,51 @@ func (s *Server) handleClient(conn net.Conn) {
for s.running {
line, err := reader.ReadBytes('\n')
if err != nil {
- s.logDebug("读取客户端请求失败: %v", err)
+ s.logDebug("Failed to read client request: %v", err)
return
}
- // 解析请求
+ // Parse request
var req Request
if err := json.Unmarshal(line, &req); err != nil {
- s.logError("解析请求失败: %v", err)
+ s.logError("Failed to parse request: %v", err)
continue
}
resp := s.handler(req)
resp.IsResponse = true
- // 发送响应
+ // Send response
respBytes, err := json.Marshal(resp)
if err != nil {
- s.logError("序列化响应失败: %v", err)
+ s.logError("Failed to serialize response: %v", err)
continue
}
_, err = conn.Write(append(respBytes, '\n'))
if err != nil {
- s.logError("发送响应失败: %v", err)
+ s.logError("Failed to send response: %v", err)
return
}
}
}
-// BroadcastEvent 广播事件给所有客户端
+// BroadcastEvent broadcasts an event to all clients
func (s *Server) BroadcastEvent(eventType string, data any) {
dataBytes, err := json.Marshal(data)
if err != nil {
- s.logError("序列化事件数据失败: %v", err)
+ s.logError("Failed to serialize event data: %v", err)
return
}
event := Event{
- IsEvent: true, // 标记为事件
+ IsEvent: true, // Mark as event
Type: eventType,
Data: dataBytes,
}
eventBytes, err := json.Marshal(event)
if err != nil {
- s.logError("序列化事件失败: %v", err)
+ s.logError("Failed to serialize event: %v", err)
return
}
@@ -252,13 +252,13 @@ func (s *Server) BroadcastEvent(eventType string, data any) {
go func(c net.Conn) {
_, err := c.Write(append(eventBytes, '\n'))
if err != nil {
- s.logDebug("发送事件失败: %v", err)
+ s.logDebug("Failed to send event: %v", err)
}
}(conn)
}
}
-// Stop 停止服务器
+// Stop stops the server
func (s *Server) Stop() {
s.running = false
if s.listener != nil {
@@ -272,17 +272,17 @@ func (s *Server) Stop() {
s.clients = make(map[net.Conn]bool)
s.mutex.Unlock()
- s.logInfo("IPC 服务器已停止")
+ s.logInfo("IPC server stopped")
}
-// HasClients 检查是否有客户端连接
+// HasClients checks if any clients are connected
func (s *Server) HasClients() bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return len(s.clients) > 0
}
-// 日志辅助方法
+// Log helper methods
func (s *Server) logInfo(format string, v ...any) {
if s.logger != nil {
s.logger.Info(format, v...)
@@ -301,7 +301,7 @@ func (s *Server) logDebug(format string, v ...any) {
}
}
-// Client IPC 客户端
+// Client IPC client
type Client struct {
conn net.Conn
mutex sync.Mutex
@@ -313,7 +313,7 @@ type Client struct {
connMutex sync.RWMutex
}
-// NewClient 创建 IPC 客户端
+// NewClient creates an IPC client
func NewClient(logger types.Logger) *Client {
return &Client{
logger: logger,
@@ -321,7 +321,7 @@ func NewClient(logger types.Logger) *Client {
}
}
-// Connect 连接到服务器
+// Connect connects to the server
func (c *Client) Connect() error {
c.connMutex.Lock()
defer c.connMutex.Unlock()
@@ -333,21 +333,21 @@ func (c *Client) Connect() error {
timeout := 5 * time.Second
conn, err := winio.DialPipe(PipePath, &timeout)
if err != nil {
- return fmt.Errorf("连接 IPC 服务器失败: %v", err)
+ return fmt.Errorf("failed to connect to IPC server: %v", err)
}
c.conn = conn
c.reader = bufio.NewReader(conn)
c.connected = true
- c.logInfo("已连接到 IPC 服务器")
+ c.logInfo("Connected to IPC server")
- // 启动消息接收循环
+ // Start message receive loop
go c.readLoop()
return nil
}
-// readLoop 统一的消息读取循环
+// readLoop unified message read loop
func (c *Client) readLoop() {
for {
c.connMutex.RLock()
@@ -360,20 +360,20 @@ func (c *Client) readLoop() {
line, err := reader.ReadBytes('\n')
if err != nil {
- c.logDebug("读取消息失败: %v", err)
+ c.logDebug("Failed to read message: %v", err)
c.connMutex.Lock()
c.connected = false
c.connMutex.Unlock()
return
}
- // 使用通用结构来检测消息类型
+ // Use generic struct to detect message type
var msg struct {
IsResponse bool `json:"isResponse"`
IsEvent bool `json:"isEvent"`
}
if err := json.Unmarshal(line, &msg); err != nil {
- c.logDebug("解析消息类型失败: %v", err)
+ c.logDebug("Failed to parse message type: %v", err)
continue
}
@@ -383,7 +383,7 @@ func (c *Client) readLoop() {
select {
case c.responseChan <- &resp:
default:
- c.logDebug("响应通道已满,丢弃响应")
+ c.logDebug("Response channel full, discarding response")
}
}
} else if msg.IsEvent {
@@ -397,12 +397,12 @@ func (c *Client) readLoop() {
}
}
-// SetEventHandler 设置事件处理函数
+// SetEventHandler sets the event handler function
func (c *Client) SetEventHandler(handler func(Event)) {
c.eventHandler = handler
}
-// SendRequest 发送请求并等待响应
+// SendRequest sends a request and waits for a response
func (c *Client) SendRequest(reqType RequestType, data any) (*Response, error) {
c.mutex.Lock()
defer c.mutex.Unlock()
@@ -410,7 +410,7 @@ func (c *Client) SendRequest(reqType RequestType, data any) (*Response, error) {
c.connMutex.RLock()
if !c.connected || c.conn == nil {
c.connMutex.RUnlock()
- return nil, fmt.Errorf("未连接到服务器")
+ return nil, fmt.Errorf("not connected to server")
}
conn := c.conn
c.connMutex.RUnlock()
@@ -420,7 +420,7 @@ func (c *Client) SendRequest(reqType RequestType, data any) (*Response, error) {
var err error
dataBytes, err = json.Marshal(data)
if err != nil {
- return nil, fmt.Errorf("序列化请求数据失败: %v", err)
+ return nil, fmt.Errorf("failed to serialize request data: %v", err)
}
}
@@ -431,10 +431,10 @@ func (c *Client) SendRequest(reqType RequestType, data any) (*Response, error) {
reqBytes, err := json.Marshal(req)
if err != nil {
- return nil, fmt.Errorf("序列化请求失败: %v", err)
+ return nil, fmt.Errorf("failed to serialize request: %v", err)
}
- // 清空响应通道
+ // Clear response channel
select {
case <-c.responseChan:
default:
@@ -442,18 +442,18 @@ func (c *Client) SendRequest(reqType RequestType, data any) (*Response, error) {
_, err = conn.Write(append(reqBytes, '\n'))
if err != nil {
- return nil, fmt.Errorf("发送请求失败: %v", err)
+ return nil, fmt.Errorf("failed to send request: %v", err)
}
select {
case resp := <-c.responseChan:
return resp, nil
case <-time.After(10 * time.Second):
- return nil, fmt.Errorf("等待响应超时")
+ return nil, fmt.Errorf("timed out waiting for response")
}
}
-// Close 关闭连接
+// Close closes the connection
func (c *Client) Close() {
c.connMutex.Lock()
defer c.connMutex.Unlock()
@@ -465,14 +465,14 @@ func (c *Client) Close() {
}
}
-// IsConnected 检查是否已连接
+// IsConnected checks if connected
func (c *Client) IsConnected() bool {
c.connMutex.RLock()
defer c.connMutex.RUnlock()
return c.connected
}
-// 日志辅助方法
+// Log helper methods
func (c *Client) logInfo(format string, v ...any) {
if c.logger != nil {
c.logger.Info(format, v...)
@@ -485,7 +485,7 @@ func (c *Client) logDebug(format string, v ...any) {
}
}
-// CheckCoreServiceRunning 检查核心服务是否正在运行
+// CheckCoreServiceRunning checks if the core service is running
func CheckCoreServiceRunning() bool {
timeout := 1 * time.Second
conn, err := winio.DialPipe(PipePath, &timeout)
@@ -496,66 +496,66 @@ func CheckCoreServiceRunning() bool {
return true
}
-// GetCoreLockFilePath 获取核心服务锁文件路径
+// GetCoreLockFilePath gets the core service lock file path
func GetCoreLockFilePath() string {
tempDir := os.TempDir()
return fmt.Sprintf("%s/bs2pro-core.lock", tempDir)
}
-// StartCoreRequestParams 启动核心服务的请求参数
+// StartCoreRequestParams request params for starting the core service
type StartCoreRequestParams struct {
ShowGUI bool `json:"showGUI"`
}
-// SetAutoControlParams 设置智能变频参数
+// SetAutoControlParams params for setting smart fan control
type SetAutoControlParams struct {
Enabled bool `json:"enabled"`
}
-// SetManualGearParams 设置手动挡位参数
+// SetManualGearParams params for setting manual gear
type SetManualGearParams struct {
Gear string `json:"gear"`
Level string `json:"level"`
}
-// SetCustomSpeedParams 设置自定义转速参数
+// SetCustomSpeedParams params for setting custom speed
type SetCustomSpeedParams struct {
Enabled bool `json:"enabled"`
RPM int `json:"rpm"`
}
-// SetBoolParams 布尔参数
+// SetBoolParams boolean params
type SetBoolParams struct {
Enabled bool `json:"enabled"`
}
-// SetStringParams 字符串参数
+// SetStringParams string params
type SetStringParams struct {
Value string `json:"value"`
}
-// SetIntParams 整数参数
+// SetIntParams integer params
type SetIntParams struct {
Value int `json:"value"`
}
-// SetAutoStartWithMethodParams 设置自启动方式参数
+// SetAutoStartWithMethodParams params for setting auto-start method
type SetAutoStartWithMethodParams struct {
Enable bool `json:"enable"`
Method string `json:"method"`
}
-// SetLightStripParams 设置灯带参数
+// SetLightStripParams params for setting light strip
type SetLightStripParams struct {
Config types.LightStripConfig `json:"config"`
}
-// SetActiveFanCurveProfileParams 设置激活曲线方案参数
+// SetActiveFanCurveProfileParams params for setting active fan curve profile
type SetActiveFanCurveProfileParams struct {
ID string `json:"id"`
}
-// SaveFanCurveProfileParams 保存曲线方案参数
+// SaveFanCurveProfileParams params for saving a fan curve profile
type SaveFanCurveProfileParams struct {
ID string `json:"id"`
Name string `json:"name"`
@@ -563,12 +563,12 @@ type SaveFanCurveProfileParams struct {
SetActive bool `json:"setActive"`
}
-// DeleteFanCurveProfileParams 删除曲线方案参数
+// DeleteFanCurveProfileParams params for deleting a fan curve profile
type DeleteFanCurveProfileParams struct {
ID string `json:"id"`
}
-// ImportFanCurveProfilesParams 导入曲线方案参数
+// ImportFanCurveProfilesParams params for importing fan curve profiles
type ImportFanCurveProfilesParams struct {
Code string `json:"code"`
}
diff --git a/internal/logger/logger.go b/internal/logger/logger.go
index 7d40e72..8e314d4 100644
--- a/internal/logger/logger.go
+++ b/internal/logger/logger.go
@@ -1,4 +1,4 @@
-// Package logger 提供基于 zap 的日志记录功能
+// Package logger provides zap-based logging functionality
package logger
import (
@@ -13,7 +13,7 @@ import (
"gopkg.in/natefinch/lumberjack.v2"
)
-// CustomLogger zap 日志记录器封装
+// CustomLogger zap logger wrapper
type CustomLogger struct {
logger *zap.Logger
sugar *zap.SugaredLogger
@@ -22,23 +22,23 @@ type CustomLogger struct {
atom zap.AtomicLevel
}
-// NewCustomLogger 创建新的日志记录器
+// NewCustomLogger creates a new logger
func NewCustomLogger(debugMode bool, installDir string) (*CustomLogger, error) {
logDir := filepath.Join(installDir, "logs")
if err := os.MkdirAll(logDir, 0755); err != nil {
- return nil, fmt.Errorf("创建日志目录失败: %v", err)
+ return nil, fmt.Errorf("failed to create log directory: %v", err)
}
- // 主日志文件路径
+ // Main log file path
logFilePath := filepath.Join(logDir, fmt.Sprintf("app_%s.log", time.Now().Format("2006-01-02")))
debugFilePath := filepath.Join(logDir, fmt.Sprintf("debug_%s.log", time.Now().Format("2006-01-02")))
- // 创建日志轮转配置
+ // Create log rotation config
appLogRotate := &lumberjack.Logger{
Filename: logFilePath,
MaxSize: 10, // MB
MaxBackups: 7,
- MaxAge: 7, // 天
+ MaxAge: 7, // days
Compress: true,
}
@@ -50,7 +50,7 @@ func NewCustomLogger(debugMode bool, installDir string) (*CustomLogger, error) {
Compress: true,
}
- // 编码器配置
+ // Encoder config
encoderConfig := zapcore.EncoderConfig{
TimeKey: "time",
LevelKey: "level",
@@ -69,7 +69,7 @@ func NewCustomLogger(debugMode bool, installDir string) (*CustomLogger, error) {
consoleEncoderConfig := encoderConfig
consoleEncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
- // 设置日志级别
+ // Set log level
atom := zap.NewAtomicLevel()
if debugMode {
atom.SetLevel(zapcore.DebugLevel)
@@ -77,7 +77,7 @@ func NewCustomLogger(debugMode bool, installDir string) (*CustomLogger, error) {
atom.SetLevel(zapcore.InfoLevel)
}
- // 创建多个核心
+ // Create multiple cores
consoleEncoder := zapcore.NewConsoleEncoder(consoleEncoderConfig)
fileEncoder := zapcore.NewJSONEncoder(encoderConfig)
@@ -95,17 +95,17 @@ func NewCustomLogger(debugMode bool, installDir string) (*CustomLogger, error) {
atom,
)
- // 控制台输出核心
+ // Console output core
consoleCore := zapcore.NewCore(
consoleEncoder,
zapcore.AddSync(os.Stdout),
atom,
)
- // 合并核心
+ // Merge cores
core := zapcore.NewTee(appCore, debugCore, consoleCore)
- // 创建 logger
+ // Create logger
logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
sugar := logger.Sugar()
@@ -118,46 +118,46 @@ func NewCustomLogger(debugMode bool, installDir string) (*CustomLogger, error) {
}, nil
}
-// Info 记录信息日志
+// Info logs an info message
func (l *CustomLogger) Info(format string, v ...any) {
l.sugar.Infof(format, v...)
}
-// Error 记录错误日志
+// Error logs an error message
func (l *CustomLogger) Error(format string, v ...any) {
l.sugar.Errorf(format, v...)
}
-// Debug 记录调试日志
+// Debug logs a debug message
func (l *CustomLogger) Debug(format string, v ...any) {
l.sugar.Debugf(format, v...)
}
-// Warn 记录警告日志
+// Warn logs a warning message
func (l *CustomLogger) Warn(format string, v ...any) {
l.sugar.Warnf(format, v...)
}
-// Fatal 记录致命错误日志并退出
+// Fatal logs a fatal error message and exits
func (l *CustomLogger) Fatal(format string, v ...any) {
l.sugar.Fatalf(format, v...)
}
-// Close 关闭日志
+// Close closes the logger
func (l *CustomLogger) Close() {
if l.logger != nil {
l.logger.Sync()
}
}
-// CleanOldLogs 清理旧日志文件(保留7天)
+// CleanOldLogs cleans up old log files (keeps 7 days)
func (l *CustomLogger) CleanOldLogs() {
files, err := os.ReadDir(l.logDir)
if err != nil {
return
}
- cutoff := time.Now().AddDate(0, 0, -7) // 7天前
+ cutoff := time.Now().AddDate(0, 0, -7) // 7 days ago
for _, file := range files {
if strings.HasSuffix(file.Name(), ".log") || strings.HasSuffix(file.Name(), ".log.gz") {
info, err := file.Info()
@@ -171,7 +171,7 @@ func (l *CustomLogger) CleanOldLogs() {
}
}
-// SetDebugMode 设置调试模式
+// SetDebugMode sets the debug mode
func (l *CustomLogger) SetDebugMode(enabled bool) {
l.debugMode = enabled
if enabled {
@@ -181,22 +181,22 @@ func (l *CustomLogger) SetDebugMode(enabled bool) {
}
}
-// GetLogDir 获取日志目录
+// GetLogDir gets the log directory
func (l *CustomLogger) GetLogDir() string {
return l.logDir
}
-// GetDebugMode 获取调试模式状态
+// GetDebugMode gets the debug mode status
func (l *CustomLogger) GetDebugMode() bool {
return l.debugMode
}
-// GetZapLogger 获取底层 zap logger
+// GetZapLogger gets the underlying zap logger
func (l *CustomLogger) GetZapLogger() *zap.Logger {
return l.logger
}
-// GetSugar 获取 sugar logger
+// GetSugar gets the sugar logger
func (l *CustomLogger) GetSugar() *zap.SugaredLogger {
return l.sugar
}
diff --git a/internal/notifier/notifier.go b/internal/notifier/notifier.go
index 0c12bc5..05e3344 100644
--- a/internal/notifier/notifier.go
+++ b/internal/notifier/notifier.go
@@ -9,7 +9,7 @@ import (
"github.com/gen2brain/beeep"
)
-// Manager 使用 beeep 发送系统通知。
+// Manager sends system notifications using beeep.
type Manager struct {
logger types.Logger
iconPath string
@@ -30,14 +30,14 @@ func (m *Manager) Notify(title, message string) {
return
}
- toastTitle := "功能变动"
+ toastTitle := "Feature Changed"
if title != "" {
toastTitle = title
}
if err := beeep.Notify(toastTitle, message, m.iconPath); err != nil {
if m.logger != nil {
- m.logger.Debug("系统通知发送失败: %v", err)
+ m.logger.Debug("Failed to send system notification: %v", err)
}
}
}
@@ -55,7 +55,7 @@ func ensureNotificationIcon(iconData []byte, logger types.Logger) string {
iconDir := filepath.Join(cacheDir, "BS2PRO-Controller")
if err := os.MkdirAll(iconDir, 0755); err != nil {
if logger != nil {
- logger.Debug("创建通知图标缓存目录失败: %v", err)
+ logger.Debug("Failed to create notification icon cache directory: %v", err)
}
return ""
}
@@ -63,7 +63,7 @@ func ensureNotificationIcon(iconData []byte, logger types.Logger) string {
iconPath := filepath.Join(iconDir, "notify-icon.ico")
if err := os.WriteFile(iconPath, iconData, 0644); err != nil {
if logger != nil {
- logger.Debug("写入通知图标缓存失败: %v", err)
+ logger.Debug("Failed to write notification icon cache: %v", err)
}
return ""
}
diff --git a/internal/smartcontrol/config.go b/internal/smartcontrol/config.go
index 4a13e25..0607cdb 100644
--- a/internal/smartcontrol/config.go
+++ b/internal/smartcontrol/config.go
@@ -2,7 +2,7 @@ package smartcontrol
import "github.com/TIANLI0/BS2PRO-Controller/internal/types"
-// NormalizeConfig 归一化智能控温配置
+// NormalizeConfig normalizes the smart temperature control configuration
func NormalizeConfig(cfg types.SmartControlConfig, curve []types.FanCurvePoint, debugMode bool) (types.SmartControlConfig, bool) {
defaults := types.GetDefaultSmartControlConfig(curve)
changed := false
@@ -133,7 +133,7 @@ func NormalizeConfig(cfg types.SmartControlConfig, curve []types.FanCurvePoint,
return cfg, changed
}
-// BlendOffsets 将升温/降温偏移融合为兼容视图
+// BlendOffsets blends heating/cooling offsets into a compatible view
func BlendOffsets(heatOffsets, coolOffsets []int) []int {
if len(heatOffsets) == 0 && len(coolOffsets) == 0 {
return nil
diff --git a/internal/smartcontrol/doc.go b/internal/smartcontrol/doc.go
index ba80b26..323d7d5 100644
--- a/internal/smartcontrol/doc.go
+++ b/internal/smartcontrol/doc.go
@@ -1,4 +1,4 @@
-// Package smartcontrol 提供学习型智能控温算法
+// Package smartcontrol provides a learning-based smart temperature control algorithm
package smartcontrol
const (
diff --git a/internal/smartcontrol/helpers.go b/internal/smartcontrol/helpers.go
index 43ce1d3..e5c8d86 100644
--- a/internal/smartcontrol/helpers.go
+++ b/internal/smartcontrol/helpers.go
@@ -6,7 +6,7 @@ func getCurveEdgeRPMBounds(curve []types.FanCurvePoint) (int, int) {
return GetCurveRPMBounds(curve)
}
-// GetCurveRPMBounds 返回用户曲线的最小/最大 RPM 边界。
+// GetCurveRPMBounds returns the min/max RPM bounds of the user curve.
func GetCurveRPMBounds(curve []types.FanCurvePoint) (int, int) {
if len(curve) == 0 {
return 0, 4000
@@ -119,13 +119,13 @@ func medianOfThree(a, b, c int) int {
return b
}
-// FilterTransientSpike 在控制环节抑制 1 个采样点的短时温度尖峰。
+// FilterTransientSpike suppresses single-sample transient temperature spikes in the control loop.
func FilterTransientSpike(currentTemp int, recentTemps []int, targetTemp, hysteresis int) (int, bool) {
if len(recentTemps) < 3 {
return currentTemp, false
}
- // 高温区优先保守,避免误抑制真实过热。
+ // In the high temperature zone, be conservative to avoid suppressing real overheating.
if currentTemp >= targetTemp+10 {
return currentTemp, false
}
@@ -140,7 +140,7 @@ func FilterTransientSpike(currentTemp int, recentTemps []int, targetTemp, hyster
return currentTemp, false
}
-// isSustainedAboveThreshold 检查最近的温度读数是否持续高于指定阈值至少 minCount 次。
+// isSustainedAboveThreshold checks if recent temperature readings have been sustained above the specified threshold for at least minCount times.
func isSustainedAboveThreshold(temps []int, threshold, minCount int) bool {
if minCount <= 0 || len(temps) < minCount {
return false
diff --git a/internal/smartcontrol/learning.go b/internal/smartcontrol/learning.go
index a79da05..37b7d67 100644
--- a/internal/smartcontrol/learning.go
+++ b/internal/smartcontrol/learning.go
@@ -2,7 +2,7 @@ package smartcontrol
import "github.com/TIANLI0/BS2PRO-Controller/internal/types"
-// LearnCurveOffsets 学习并更新曲线偏移
+// LearnCurveOffsets learns and updates curve offsets
func LearnCurveOffsets(avgTemp, lastAvgTemp, targetRPM, lastTargetRPM int, recentAvgTemps []int, curve []types.FanCurvePoint, cfg types.SmartControlConfig) ([]int, []int, []int, []int, bool) {
if len(curve) == 0 {
rateHeat, _ := normalizeRateBiases(cfg.LearnedRateHeat, cfg.MaxLearnOffset)
@@ -92,7 +92,7 @@ func LearnCurveOffsets(avgTemp, lastAvgTemp, targetRPM, lastTargetRPM int, recen
return heatOffsets, coolOffsets, rateHeat, rateCool, false
}
- // 将评分压缩为小步进,避免学习曲线过于陡峭。
+ // Compress the score into small steps to avoid an overly steep learning curve.
denominator := max(10, 24-cfg.LearnRate*2)
delta := raw / denominator
if delta == 0 {
diff --git a/internal/smartcontrol/target.go b/internal/smartcontrol/target.go
index f74513d..0ff7735 100644
--- a/internal/smartcontrol/target.go
+++ b/internal/smartcontrol/target.go
@@ -5,7 +5,7 @@ import (
"github.com/TIANLI0/BS2PRO-Controller/internal/types"
)
-// CalculateTargetRPM 计算智能目标转速
+// CalculateTargetRPM calculates the smart target RPM
func CalculateTargetRPM(avgTemp, lastAvgTemp int, curve []types.FanCurvePoint, cfg types.SmartControlConfig) int {
effectiveCurve := make([]types.FanCurvePoint, len(curve))
activeOffsets := selectOffsetsForTrend(avgTemp-lastAvgTemp, cfg)
@@ -100,7 +100,7 @@ func sampleRateBias(tempDelta int, cfg types.SmartControlConfig) int {
return rateBiases[rateBucketIndex(tempDelta)]
}
-// ApplyRampLimit 应用升降速限幅
+// ApplyRampLimit applies ramp-up/ramp-down rate limiting
func ApplyRampLimit(targetRPM, lastRPM, upLimit, downLimit int) int {
if targetRPM > lastRPM {
return min(lastRPM+upLimit, targetRPM)
diff --git a/internal/temperature/temperature.go b/internal/temperature/temperature.go
index f8d7dba..23c1767 100644
--- a/internal/temperature/temperature.go
+++ b/internal/temperature/temperature.go
@@ -1,4 +1,4 @@
-// Package temperature 提供温度读取功能
+// Package temperature provides temperature reading functionality
package temperature
import (
@@ -13,13 +13,13 @@ import (
"github.com/shirou/gopsutil/v4/sensors"
)
-// Reader 温度读取器
+// Reader is a temperature reader
type Reader struct {
bridgeManager *bridge.Manager
logger types.Logger
}
-// NewReader 创建新的温度读取器
+// NewReader creates a new temperature reader
func NewReader(bridgeManager *bridge.Manager, logger types.Logger) *Reader {
return &Reader{
bridgeManager: bridgeManager,
@@ -27,20 +27,20 @@ func NewReader(bridgeManager *bridge.Manager, logger types.Logger) *Reader {
}
}
-// Read 读取温度
+// Read reads the temperature
func (r *Reader) Read() types.TemperatureData {
temp := types.TemperatureData{
UpdateTime: time.Now().Unix(),
BridgeOk: true,
}
- // 优先使用桥接程序读取温度
+ // Prefer using the bridge program to read temperature
bridgeTemp := r.bridgeManager.GetTemperature()
if bridgeTemp.Success {
if bridgeTemp.CpuTemp == 0 && bridgeTemp.GpuTemp == 0 {
temp.BridgeOk = false
- temp.BridgeMsg = "桥接程序返回空温度(CPU/GPU 均为 0),请重启软件或重新安装 PawnIO 驱动。"
- r.logger.Warn("桥接程序返回空温度数据,使用备用方法")
+ temp.BridgeMsg = "Bridge returned empty temperature (both CPU/GPU are 0). Please restart the software or reinstall the PawnIO driver."
+ r.logger.Warn("Bridge returned empty temperature data, using fallback method")
temp.CPUTemp = r.readCPUTemperature()
temp.GPUTemp = r.readGPUTemperature()
@@ -56,32 +56,32 @@ func (r *Reader) Read() types.TemperatureData {
return temp
}
- // 如果桥接程序失败,使用备用方法
- r.logger.Warn("桥接程序读取温度失败: %s, 使用备用方法", bridgeTemp.Error)
+ // If the bridge program fails, use fallback method
+ r.logger.Warn("Bridge failed to read temperature: %s, using fallback method", bridgeTemp.Error)
temp.BridgeOk = false
temp.BridgeMsg = bridgeTemp.Error
if strings.TrimSpace(temp.BridgeMsg) == "" {
- temp.BridgeMsg = "CPU/GPU 温度读取失败,请检查 PawnIO 是否安装成功,或升级最新版。"
+ temp.BridgeMsg = "Failed to read CPU/GPU temperature. Please check if PawnIO is installed correctly, or upgrade to the latest version."
}
- // 读取CPU温度
+ // Read CPU temperature
temp.CPUTemp = r.readCPUTemperature()
- // 读取GPU温度
+ // Read GPU temperature
temp.GPUTemp = r.readGPUTemperature()
- // 计算最高温度
+ // Calculate maximum temperature
temp.MaxTemp = max(temp.CPUTemp, temp.GPUTemp)
return temp
}
-// readCPUTemperature 读取CPU温度
+// readCPUTemperature reads the CPU temperature
func (r *Reader) readCPUTemperature() int {
sensorTemps, err := sensors.SensorsTemperatures()
if err == nil {
for _, sensor := range sensorTemps {
- // 查找ACPI ThermalZone TZ00_0或类似的CPU温度传感器
+ // Look for ACPI ThermalZone TZ00_0 or similar CPU temperature sensors
if strings.Contains(strings.ToLower(sensor.SensorKey), "tz00") ||
strings.Contains(strings.ToLower(sensor.SensorKey), "cpu") ||
strings.Contains(strings.ToLower(sensor.SensorKey), "core") {
@@ -90,21 +90,21 @@ func (r *Reader) readCPUTemperature() int {
}
}
- // 如果传感器方式失败,尝试通过WMI (Windows)
+ // If sensor method fails, try via WMI (Windows)
return r.readWindowsCPUTemp()
}
-// readGPUTemperature 读取GPU温度
+// readGPUTemperature reads the GPU temperature
func (r *Reader) readGPUTemperature() int {
vendor := r.detectGPUVendor()
return r.readGPUTempByVendor(vendor)
}
-// readWindowsCPUTemp 通过WMI读取Windows CPU温度
+// readWindowsCPUTemp reads Windows CPU temperature via WMI
func (r *Reader) readWindowsCPUTemp() int {
output, err := execCommandHidden("wmic", "/namespace:\\\\root\\wmi", "PATH", "MSAcpi_ThermalZoneTemperature", "get", "CurrentTemperature", "/value")
if err != nil {
- r.logger.Debug("读取Windows CPU温度失败: %v", err)
+ r.logger.Debug("Failed to read Windows CPU temperature: %v", err)
return 0
}
@@ -128,9 +128,9 @@ func (r *Reader) readWindowsCPUTemp() int {
return 0
}
-// detectGPUVendor 检测GPU厂商
+// detectGPUVendor detects the GPU vendor
func (r *Reader) detectGPUVendor() string {
- // 尝试NVIDIA
+ // Try NVIDIA
if _, err := execCommandHidden("nvidia-smi", "--version"); err == nil {
return "nvidia"
}
@@ -138,7 +138,7 @@ func (r *Reader) detectGPUVendor() string {
return "unknown"
}
-// readGPUTempByVendor 根据厂商读取GPU温度
+// readGPUTempByVendor reads GPU temperature based on vendor
func (r *Reader) readGPUTempByVendor(vendor string) int {
switch vendor {
case "nvidia":
@@ -150,11 +150,11 @@ func (r *Reader) readGPUTempByVendor(vendor string) int {
}
}
-// readNvidiaGPUTemp 安全读取NVIDIA GPU温度
+// readNvidiaGPUTemp safely reads NVIDIA GPU temperature
func (r *Reader) readNvidiaGPUTemp() int {
output, err := execCommandHidden("nvidia-smi", "--query-gpu=temperature.gpu", "--format=csv,noheader,nounits")
if err != nil {
- r.logger.Debug("读取NVIDIA GPU温度失败: %v", err)
+ r.logger.Debug("Failed to read NVIDIA GPU temperature: %v", err)
return 0
}
@@ -170,7 +170,7 @@ func (r *Reader) readNvidiaGPUTemp() int {
return 0
}
-// execCommandHidden 执行命令并隐藏窗口
+// execCommandHidden executes a command with a hidden window
func execCommandHidden(name string, args ...string) ([]byte, error) {
cmd := exec.Command(name, args...)
@@ -181,7 +181,7 @@ func execCommandHidden(name string, args ...string) ([]byte, error) {
return cmd.Output()
}
-// CalculateTargetRPM 根据温度计算目标转速
+// CalculateTargetRPM calculates target RPM based on temperature
func CalculateTargetRPM(temperature int, fanCurve []types.FanCurvePoint) int {
if len(fanCurve) < 2 {
return 0
@@ -196,13 +196,13 @@ func CalculateTargetRPM(temperature int, fanCurve []types.FanCurvePoint) int {
return lastPoint.RPM
}
- // 线性插值计算转速
+ // Calculate RPM using linear interpolation
for i := 0; i < len(fanCurve)-1; i++ {
p1 := fanCurve[i]
p2 := fanCurve[i+1]
if temperature >= p1.Temperature && temperature <= p2.Temperature {
- // 线性插值
+ // Linear interpolation
ratio := float64(temperature-p1.Temperature) / float64(p2.Temperature-p1.Temperature)
rpm := float64(p1.RPM) + ratio*float64(p2.RPM-p1.RPM)
return int(rpm)
diff --git a/internal/tray/tray.go b/internal/tray/tray.go
index a55ff3d..dc81087 100644
--- a/internal/tray/tray.go
+++ b/internal/tray/tray.go
@@ -1,4 +1,4 @@
-// Package tray 提供系统托盘管理功能
+// Package tray provides system tray management functionality
package tray
import (
@@ -12,13 +12,13 @@ import (
"github.com/TIANLI0/BS2PRO-Controller/internal/types"
)
-// Manager 系统托盘管理器
+// Manager is the system tray manager
type Manager struct {
logger types.Logger
- initialized int32 // atomic: 0=未初始化, 1=已初始化
- readyState int32 // atomic: 0=未就绪, 1=就绪
+ initialized int32 // atomic: 0=not initialized, 1=initialized
+ readyState int32 // atomic: 0=not ready, 1=ready
mutex sync.Mutex
- done chan struct{} // 关闭此通道以通知所有 goroutine 退出
+ done chan struct{} // close this channel to notify all goroutines to exit
uiQueue chan func()
iconData []byte
menuItems *MenuItems
@@ -30,17 +30,17 @@ type Manager struct {
getStatus func() Status
curveMenuItems map[string]*systray.MenuItem
- // 监控托盘健康状态
+ // Monitor tray health status
lastIconRefresh int64
- consecutiveFails int32 // 连续失败计数
+ consecutiveFails int32 // consecutive failure count
- // 防止托盘动作重入导致偶发无响应
+ // Prevent tray action re-entry causing occasional unresponsiveness
showWindowInFlight int32
toggleAutoInFlight int32
quitInFlight int32
}
-// MenuItems 托盘菜单项结构
+// MenuItems defines tray menu item structure
type MenuItems struct {
Show *systray.MenuItem
DeviceStatus *systray.MenuItem
@@ -52,13 +52,13 @@ type MenuItems struct {
Quit *systray.MenuItem
}
-// CurveOption 托盘曲线选项
+// CurveOption defines a tray curve option
type CurveOption struct {
ID string
Name string
}
-// Status 状态信息
+// Status holds status information
type Status struct {
Connected bool
CPUTemp int
@@ -69,7 +69,7 @@ type Status struct {
CurveProfiles []CurveOption
}
-// NewManager 创建新的托盘管理器
+// NewManager creates a new tray manager
func NewManager(logger types.Logger, iconData []byte) *Manager {
return &Manager{
logger: logger,
@@ -80,7 +80,7 @@ func NewManager(logger types.Logger, iconData []byte) *Manager {
}
}
-// SetCallbacks 设置回调函数
+// SetCallbacks sets callback functions
func (m *Manager) SetCallbacks(
onShowWindow func(),
onQuit func(),
@@ -97,18 +97,18 @@ func (m *Manager) SetCallbacks(
m.getStatus = getStatus
}
-// Init 初始化系统托盘
+// Init initializes the system tray
func (m *Manager) Init() {
m.mutex.Lock()
defer m.mutex.Unlock()
- // 检查是否已经初始化
+ // Check if already initialized
if !atomic.CompareAndSwapInt32(&m.initialized, 0, 1) {
- m.logDebug("托盘已经初始化,跳过重复初始化")
+ m.logDebug("Tray already initialized, skipping duplicate initialization")
return
}
- m.logInfo("正在初始化系统托盘")
+ m.logInfo("Initializing system tray")
go func() {
runtime.LockOSThread()
@@ -116,7 +116,7 @@ func (m *Manager) Init() {
defer func() {
if r := recover(); r != nil {
- m.logError("托盘初始化过程中发生panic: %v", r)
+ m.logError("Panic during tray initialization: %v", r)
atomic.StoreInt32(&m.initialized, 0)
atomic.StoreInt32(&m.readyState, 0)
}
@@ -126,38 +126,38 @@ func (m *Manager) Init() {
}()
}
-// onTrayReady 托盘准备就绪时的回调
+// onTrayReady is the callback when the tray is ready
func (m *Manager) onTrayReady() {
defer func() {
if r := recover(); r != nil {
- m.logError("托盘回调函数中发生panic: %v", r)
+ m.logError("Panic in tray callback: %v", r)
atomic.StoreInt32(&m.initialized, 0)
atomic.StoreInt32(&m.readyState, 0)
}
}()
- m.logInfo("托盘回调函数已启动")
+ m.logInfo("Tray callback started")
if err := m.setupIcon(); err != nil {
- m.logError("设置托盘图标失败: %v", err)
+ m.logError("Failed to set tray icon: %v", err)
atomic.StoreInt32(&m.readyState, 0)
atomic.StoreInt32(&m.initialized, 0)
systray.Quit()
return
}
- // 左键单击托盘图标:显示主窗口;右键保持默认行为(打开托盘菜单)
+ // Left-click tray icon: show main window; right-click keeps default behavior (open tray menu)
systray.SetOnTapped(func() {
- m.logDebug("托盘图标左键点击: 显示主窗口")
+ m.logDebug("Tray icon left-clicked: showing main window")
if m.onShowWindow != nil {
m.runTrayActionAsync("icon-show-window", &m.showWindowInFlight, m.onShowWindow)
}
})
- // 创建托盘菜单
+ // Create tray menu
menuItems, err := m.createMenu()
if err != nil {
- m.logError("创建托盘菜单失败: %v", err)
+ m.logError("Failed to create tray menu: %v", err)
atomic.StoreInt32(&m.readyState, 0)
atomic.StoreInt32(&m.initialized, 0)
systray.Quit()
@@ -169,61 +169,61 @@ func (m *Manager) onTrayReady() {
atomic.StoreInt32(&m.readyState, 1)
atomic.StoreInt64(&m.lastIconRefresh, time.Now().Unix())
atomic.StoreInt32(&m.consecutiveFails, 0)
- m.logInfo("系统托盘初始化完成")
+ m.logInfo("System tray initialization complete")
- // 处理托盘菜单事件
+ // Handle tray menu events
go m.handleMenuEvents()
- // 定期更新托盘菜单状态
+ // Periodically update tray menu status
go m.updateMenuStatus()
- // 启动托盘健康监控(定期刷新图标以应对 Explorer 重启等)
+ // Start tray icon health monitor (periodically refresh icon to handle Explorer restarts, etc.)
go m.startIconHealthMonitor()
}
-// setupIcon 设置托盘图标
+// setupIcon sets up the tray icon
func (m *Manager) setupIcon() (err error) {
defer func() {
if r := recover(); r != nil {
- err = fmt.Errorf("设置托盘图标时发生panic: %v", r)
+ err = fmt.Errorf("panic while setting tray icon: %v", r)
}
}()
if len(m.iconData) == 0 {
- return fmt.Errorf("托盘图标数据为空")
+ return fmt.Errorf("tray icon data is empty")
}
systray.SetIcon(m.iconData)
- systray.SetTitle("BS2PRO 控制器")
- systray.SetTooltip("BS2PRO 风扇控制器 - 运行中")
+ systray.SetTitle("BS2PRO Controller")
+ systray.SetTooltip("BS2PRO Fan Controller - Running")
return nil
}
-// createMenu 创建托盘菜单
+// createMenu creates the tray menu
func (m *Manager) createMenu() (items *MenuItems, err error) {
defer func() {
if r := recover(); r != nil {
- err = fmt.Errorf("创建托盘菜单时发生panic: %v", r)
+ err = fmt.Errorf("panic while creating tray menu: %v", r)
}
}()
items = &MenuItems{}
- items.Show = systray.AddMenuItem("显示主窗口", "显示控制器主窗口")
+ items.Show = systray.AddMenuItem("Show Main Window", "Show the controller main window")
systray.AddSeparator()
- items.DeviceStatus = systray.AddMenuItem("设备状态", "查看设备连接状态")
+ items.DeviceStatus = systray.AddMenuItem("Device Status", "View device connection status")
items.DeviceStatus.Disable()
- items.CPUTemperature = systray.AddMenuItem("CPU温度", "显示当前CPU温度")
+ items.CPUTemperature = systray.AddMenuItem("CPU Temperature", "Show current CPU temperature")
items.CPUTemperature.Disable()
- items.GPUTemperature = systray.AddMenuItem("GPU温度", "显示当前GPU温度")
+ items.GPUTemperature = systray.AddMenuItem("GPU Temperature", "Show current GPU temperature")
items.GPUTemperature.Disable()
- items.FanSpeed = systray.AddMenuItem("风扇转速", "显示当前风扇转速")
+ items.FanSpeed = systray.AddMenuItem("Fan Speed", "Show current fan speed")
items.FanSpeed.Disable()
- items.CurveSelect = systray.AddMenuItem("选择温控曲线", "直接切换到指定温控曲线")
+ items.CurveSelect = systray.AddMenuItem("Select Fan Curve", "Switch to a specific fan curve")
if m.getCurveOptions != nil {
profiles, activeID := m.getCurveOptions()
@@ -231,41 +231,41 @@ func (m *Manager) createMenu() (items *MenuItems, err error) {
m.updateCurveMenuSelection(activeID)
}
- // 智能变频状态 - 获取当前配置状态
+ // Smart control state - get current configuration state
autoControlEnabled := false
if m.getStatus != nil {
autoControlEnabled = m.getStatus().AutoControlState
}
- items.AutoControl = systray.AddMenuItemCheckbox("智能变频", "启用/禁用智能变频", autoControlEnabled)
+ items.AutoControl = systray.AddMenuItemCheckbox("Smart Control", "Enable/Disable smart control", autoControlEnabled)
systray.AddSeparator()
- items.Quit = systray.AddMenuItem("退出", "完全退出应用")
+ items.Quit = systray.AddMenuItem("Quit", "Completely exit the application")
return items, nil
}
-// handleMenuEvents 处理托盘菜单事件
+// handleMenuEvents handles tray menu events
func (m *Manager) handleMenuEvents() {
defer func() {
if r := recover(); r != nil {
- m.logError("处理托盘菜单事件时发生panic: %v", r)
+ m.logError("Panic while handling tray menu events: %v", r)
}
}()
if m.menuItems == nil || m.menuItems.Show == nil || m.menuItems.AutoControl == nil || m.menuItems.Quit == nil {
- m.logError("托盘菜单未正确初始化,无法处理菜单事件")
+ m.logError("Tray menu not properly initialized, cannot handle menu events")
return
}
for {
select {
case <-m.menuItems.Show.ClickedCh:
- m.logDebug("托盘菜单: 显示主窗口")
+ m.logDebug("Tray menu: show main window")
if m.onShowWindow != nil {
m.runTrayActionAsync("menu-show-window", &m.showWindowInFlight, m.onShowWindow)
}
case <-m.menuItems.AutoControl.ClickedCh:
- m.logDebug("托盘菜单: 切换智能变频状态")
+ m.logDebug("Tray menu: toggle smart control state")
if m.onToggleAuto != nil {
m.runTrayActionAsync("menu-toggle-auto", &m.toggleAutoInFlight, func() {
newState := m.onToggleAuto()
@@ -282,7 +282,7 @@ func (m *Manager) handleMenuEvents() {
})
}
case <-m.menuItems.Quit.ClickedCh:
- m.logInfo("托盘菜单: 用户请求退出应用")
+ m.logInfo("Tray menu: user requested to quit application")
if m.onQuit != nil {
m.runTrayActionAsync("menu-quit", &m.quitInFlight, m.onQuit)
}
@@ -293,14 +293,14 @@ func (m *Manager) handleMenuEvents() {
}
}
-// runTrayActionAsync 异步执行托盘动作,避免阻塞托盘消息处理
+// runTrayActionAsync asynchronously executes tray actions to avoid blocking tray message processing
func (m *Manager) runTrayActionAsync(action string, inFlight *int32, fn func()) {
if fn == nil {
return
}
if inFlight != nil && !atomic.CompareAndSwapInt32(inFlight, 0, 1) {
- m.logDebug("托盘动作[%s]仍在执行,忽略重复触发", action)
+ m.logDebug("Tray action [%s] still in progress, ignoring duplicate trigger", action)
return
}
@@ -311,14 +311,14 @@ func (m *Manager) runTrayActionAsync(action string, inFlight *int32, fn func())
atomic.StoreInt32(inFlight, 0)
}
if r := recover(); r != nil {
- m.logError("托盘动作[%s]发生panic: %v", action, r)
+ m.logError("Panic in tray action [%s]: %v", action, r)
}
d := time.Since(startedAt)
if d > 800*time.Millisecond {
- m.logError("托盘动作[%s]执行耗时较长: %v", action, d)
+ m.logError("Tray action [%s] took too long: %v", action, d)
} else {
- m.logDebug("托盘动作[%s]执行完成: %v", action, d)
+ m.logDebug("Tray action [%s] completed: %v", action, d)
}
}()
@@ -326,11 +326,11 @@ func (m *Manager) runTrayActionAsync(action string, inFlight *int32, fn func())
}()
}
-// updateMenuStatus 定期更新托盘菜单状态
+// updateMenuStatus periodically updates tray menu status
func (m *Manager) updateMenuStatus() {
defer func() {
if r := recover(); r != nil {
- m.logError("更新托盘菜单状态时发生panic: %v", r)
+ m.logError("Panic while updating tray menu status: %v", r)
}
}()
@@ -340,7 +340,7 @@ func (m *Manager) updateMenuStatus() {
for {
select {
case <-ticker.C:
- // 如果托盘不可用,跳过本次更新但不退出,等待恢复
+ // If tray is unavailable, skip this update but don't exit, wait for recovery
if atomic.LoadInt32(&m.readyState) == 0 || atomic.LoadInt32(&m.initialized) == 0 {
continue
}
@@ -356,27 +356,27 @@ func (m *Manager) updateMenuStatus() {
}
if status.Connected {
- m.menuItems.DeviceStatus.SetTitle("设备状态: 已连接")
+ m.menuItems.DeviceStatus.SetTitle("Device Status: Connected")
} else {
- m.menuItems.DeviceStatus.SetTitle("设备状态: 未连接")
+ m.menuItems.DeviceStatus.SetTitle("Device Status: Disconnected")
}
if status.CPUTemp > 0 {
- m.menuItems.CPUTemperature.SetTitle(fmt.Sprintf("CPU温度: %d°C", status.CPUTemp))
+ m.menuItems.CPUTemperature.SetTitle(fmt.Sprintf("CPU Temp: %d°C", status.CPUTemp))
} else {
- m.menuItems.CPUTemperature.SetTitle("CPU温度: 无数据")
+ m.menuItems.CPUTemperature.SetTitle("CPU Temp: No Data")
}
if status.GPUTemp > 0 {
- m.menuItems.GPUTemperature.SetTitle(fmt.Sprintf("GPU温度: %d°C", status.GPUTemp))
+ m.menuItems.GPUTemperature.SetTitle(fmt.Sprintf("GPU Temp: %d°C", status.GPUTemp))
} else {
- m.menuItems.GPUTemperature.SetTitle("GPU温度: 无数据")
+ m.menuItems.GPUTemperature.SetTitle("GPU Temp: No Data")
}
if status.CurrentRPM > 0 {
- m.menuItems.FanSpeed.SetTitle(fmt.Sprintf("风扇转速: %d RPM", status.CurrentRPM))
+ m.menuItems.FanSpeed.SetTitle(fmt.Sprintf("Fan Speed: %d RPM", status.CurrentRPM))
} else {
- m.menuItems.FanSpeed.SetTitle("风扇转速: 无数据")
+ m.menuItems.FanSpeed.SetTitle("Fan Speed: No Data")
}
if m.menuItems.CurveSelect != nil {
@@ -392,20 +392,20 @@ func (m *Manager) updateMenuStatus() {
if status.Connected {
if status.AutoControlState {
- tooltipText := fmt.Sprintf("BS2PRO 控制器 - 智能变频中\nCPU: %d°C GPU: %d°C", status.CPUTemp, status.GPUTemp)
+ tooltipText := fmt.Sprintf("BS2PRO Controller - Smart Control Active\nCPU: %d°C GPU: %d°C", status.CPUTemp, status.GPUTemp)
if status.CurrentRPM > 0 {
- tooltipText += fmt.Sprintf("\n风扇: %d RPM", status.CurrentRPM)
+ tooltipText += fmt.Sprintf("\nFan: %d RPM", status.CurrentRPM)
}
systray.SetTooltip(tooltipText)
} else {
- tooltipText := "BS2PRO 控制器 - 手动模式"
+ tooltipText := "BS2PRO Controller - Manual Mode"
if status.CurrentRPM > 0 {
- tooltipText += fmt.Sprintf("\n风扇: %d RPM", status.CurrentRPM)
+ tooltipText += fmt.Sprintf("\nFan: %d RPM", status.CurrentRPM)
}
systray.SetTooltip(tooltipText)
}
} else {
- systray.SetTooltip("BS2PRO 控制器 - 设备未连接")
+ systray.SetTooltip("BS2PRO Controller - Device Disconnected")
}
})
case <-m.done:
@@ -414,22 +414,22 @@ func (m *Manager) updateMenuStatus() {
}
}
-// onTrayExit 托盘退出时的回调
+// onTrayExit is the callback when the tray exits
func (m *Manager) onTrayExit() {
- m.logDebug("托盘退出回调被触发")
+ m.logDebug("Tray exit callback triggered")
atomic.StoreInt32(&m.readyState, 0)
atomic.StoreInt32(&m.initialized, 0)
}
-// startIconHealthMonitor 启动托盘图标健康监控
+// startIconHealthMonitor starts tray icon health monitoring
func (m *Manager) startIconHealthMonitor() {
defer func() {
if r := recover(); r != nil {
- m.logError("托盘图标健康监控发生panic: %v", r)
+ m.logError("Panic in tray icon health monitor: %v", r)
}
}()
- // 每30秒刷新一次托盘图标,更及时地恢复 Explorer 重启后的图标
+ // Refresh tray icon every 30 seconds to recover icon more promptly after Explorer restarts
ticker := time.NewTicker(30 * time.Second)
defer ticker.Stop()
@@ -437,7 +437,7 @@ func (m *Manager) startIconHealthMonitor() {
select {
case <-ticker.C:
if atomic.LoadInt32(&m.readyState) == 0 || atomic.LoadInt32(&m.initialized) == 0 {
- continue // 不退出,等待恢复
+ continue // don't exit, wait for recovery
}
m.refreshTrayIcon()
case <-m.done:
@@ -446,11 +446,11 @@ func (m *Manager) startIconHealthMonitor() {
}
}
-// refreshTrayIcon 刷新托盘图标
+// refreshTrayIcon refreshes the tray icon
func (m *Manager) refreshTrayIcon() {
defer func() {
if r := recover(); r != nil {
- m.logError("刷新托盘图标时发生panic: %v", r)
+ m.logError("Panic while refreshing tray icon: %v", r)
atomic.AddInt32(&m.consecutiveFails, 1)
}
}()
@@ -458,17 +458,17 @@ func (m *Manager) refreshTrayIcon() {
queued := m.enqueueUI("refresh-tray-icon", func() {
if len(m.iconData) == 0 {
atomic.AddInt32(&m.consecutiveFails, 1)
- m.logError("刷新托盘图标失败: 图标数据为空")
+ m.logError("Failed to refresh tray icon: icon data is empty")
return
}
systray.SetIcon(m.iconData)
- systray.SetTooltip("BS2PRO 风扇控制器 - 运行中")
+ systray.SetTooltip("BS2PRO Fan Controller - Running")
atomic.StoreInt32(&m.consecutiveFails, 0)
atomic.StoreInt64(&m.lastIconRefresh, time.Now().Unix())
- m.logDebug("托盘图标已刷新")
+ m.logDebug("Tray icon refreshed")
})
if !queued {
@@ -480,7 +480,7 @@ func (m *Manager) startUIWorker() {
go func() {
defer func() {
if r := recover(); r != nil {
- m.logError("托盘UI队列处理发生panic: %v", r)
+ m.logError("Panic in tray UI queue processing: %v", r)
}
}()
@@ -511,7 +511,7 @@ func (m *Manager) enqueueUI(action string, fn func()) bool {
wrapped := func() {
defer func() {
if r := recover(); r != nil {
- m.logError("托盘UI动作[%s]发生panic: %v", action, r)
+ m.logError("Panic in tray UI action [%s]: %v", action, r)
}
}()
fn()
@@ -521,29 +521,29 @@ func (m *Manager) enqueueUI(action string, fn func()) bool {
case m.uiQueue <- wrapped:
return true
default:
- m.logError("托盘UI队列繁忙,丢弃动作: %s", action)
+ m.logError("Tray UI queue busy, dropping action: %s", action)
return false
}
}
-// IsReady 检查托盘是否就绪
+// IsReady checks if the tray is ready
func (m *Manager) IsReady() bool {
return atomic.LoadInt32(&m.readyState) == 1
}
-// IsInitialized 检查托盘是否已初始化
+// IsInitialized checks if the tray has been initialized
func (m *Manager) IsInitialized() bool {
return atomic.LoadInt32(&m.initialized) == 1
}
-// Quit 退出托盘
+// Quit exits the tray
func (m *Manager) Quit() {
atomic.StoreInt32(&m.readyState, 0)
m.mutex.Lock()
select {
case <-m.done:
- // 已经关闭
+ // already closed
default:
close(m.done)
}
@@ -552,41 +552,41 @@ func (m *Manager) Quit() {
func() {
defer func() {
if r := recover(); r != nil {
- m.logDebug("退出托盘时发生错误(可忽略): %v", r)
+ m.logDebug("Error while quitting tray (can be ignored): %v", r)
}
}()
systray.Quit()
}()
}
-// CheckHealth 检查托盘健康状态
+// CheckHealth checks the tray health status
func (m *Manager) CheckHealth() {
defer func() {
if r := recover(); r != nil {
- m.logError("检查托盘健康状态时发生panic: %v", r)
+ m.logError("Panic while checking tray health: %v", r)
}
}()
- // 如果托盘未初始化,无需检查
+ // If tray is not initialized, no need to check
if atomic.LoadInt32(&m.initialized) == 0 {
return
}
- // 检查图标是否长时间未刷新
+ // Check if icon has not been refreshed for a long time
lastRefresh := atomic.LoadInt64(&m.lastIconRefresh)
if lastRefresh > 0 && time.Now().Unix()-lastRefresh > 90 {
- m.logInfo("检测到托盘图标长时间未刷新,尝试刷新")
+ m.logInfo("Detected tray icon not refreshed for a long time, attempting refresh")
m.refreshTrayIcon()
}
- // 如果连续失败,也强制刷新图标
+ // If there are consecutive failures, also force refresh the icon
if atomic.LoadInt32(&m.consecutiveFails) >= 3 {
- m.logError("检测到托盘连续失败,尝试刷新图标")
+ m.logError("Detected consecutive tray failures, attempting to refresh icon")
m.refreshTrayIcon()
}
}
-// 日志辅助方法
+// Log helper methods
func (m *Manager) logInfo(format string, v ...any) {
if m.logger != nil {
m.logger.Info(format, v...)
@@ -612,7 +612,7 @@ func (m *Manager) ensureCurveMenuItems(parent *systray.MenuItem, options []Curve
if len(options) == 0 {
if len(m.curveMenuItems) == 0 {
- emptyItem := parent.AddSubMenuItem("暂无可用曲线", "")
+ emptyItem := parent.AddSubMenuItem("No curves available", "")
emptyItem.Disable()
m.curveMenuItems["__empty__"] = emptyItem
}
@@ -650,7 +650,7 @@ func (m *Manager) ensureCurveMenuItems(parent *systray.MenuItem, options []Curve
continue
}
- item := parent.AddSubMenuItemCheckbox(option.Name, "切换温控曲线", false)
+ item := parent.AddSubMenuItemCheckbox(option.Name, "Switch fan curve", false)
m.curveMenuItems[option.ID] = item
profileID := option.ID
diff --git a/internal/types/types.go b/internal/types/types.go
index 8ad4fac..f2e8f85 100644
--- a/internal/types/types.go
+++ b/internal/types/types.go
@@ -1,26 +1,26 @@
-// Package types 定义了 BS2PRO 控制器应用中使用的所有共享类型
+// Package types defines all shared types used in the BS2PRO controller application
package types
-// FanCurvePoint 风扇曲线点
+// FanCurvePoint represents a point on the fan curve
type FanCurvePoint struct {
- Temperature int `json:"temperature"` // 温度 °C
- RPM int `json:"rpm"` // 转速 RPM
+ Temperature int `json:"temperature"` // Temperature in °C
+ RPM int `json:"rpm"` // Speed in RPM
}
-// FanCurveProfile 温控曲线方案
+// FanCurveProfile represents a fan curve profile
type FanCurveProfile struct {
ID string `json:"id"`
Name string `json:"name"`
Curve []FanCurvePoint `json:"curve"`
}
-// FanCurveProfilesPayload 风扇曲线方案返回载荷
+// FanCurveProfilesPayload is the payload returned for fan curve profiles
type FanCurveProfilesPayload struct {
Profiles []FanCurveProfile `json:"profiles"`
ActiveID string `json:"activeId"`
}
-// FanData 风扇数据结构
+// FanData represents the fan data structure
type FanData struct {
ReportID uint8 `json:"reportId"`
MagicSync uint16 `json:"magicSync"`
@@ -36,24 +36,24 @@ type FanData struct {
WorkMode string `json:"workMode"`
}
-// GearCommand 挡位命令结构
+// GearCommand represents a gear command structure
type GearCommand struct {
- Name string `json:"name"` // 挡位名称
- Command []byte `json:"command"` // 命令字节
- RPM int `json:"rpm"` // 对应转速
+ Name string `json:"name"` // Gear name
+ Command []byte `json:"command"` // Command bytes
+ RPM int `json:"rpm"` // Corresponding RPM
}
-// TemperatureData 温度数据
+// TemperatureData holds temperature data
type TemperatureData struct {
- CPUTemp int `json:"cpuTemp"` // CPU温度
- GPUTemp int `json:"gpuTemp"` // GPU温度
- MaxTemp int `json:"maxTemp"` // 最高温度
- UpdateTime int64 `json:"updateTime"` // 更新时间戳
- BridgeOk bool `json:"bridgeOk"` // 桥接程序是否正常
- BridgeMsg string `json:"bridgeMessage"` // 桥接故障提示
+ CPUTemp int `json:"cpuTemp"` // CPU temperature
+ GPUTemp int `json:"gpuTemp"` // GPU temperature
+ MaxTemp int `json:"maxTemp"` // Maximum temperature
+ UpdateTime int64 `json:"updateTime"` // Update timestamp
+ BridgeOk bool `json:"bridgeOk"` // Whether the bridge is working normally
+ BridgeMsg string `json:"bridgeMessage"` // Bridge fault message
}
-// BridgeTemperatureData 桥接程序返回的温度数据
+// BridgeTemperatureData represents temperature data returned by the bridge program
type BridgeTemperatureData struct {
CpuTemp int `json:"cpuTemp"`
GpuTemp int `json:"gpuTemp"`
@@ -63,89 +63,89 @@ type BridgeTemperatureData struct {
Error string `json:"error"`
}
-// BridgeCommand 桥接程序命令
+// BridgeCommand represents a bridge program command
type BridgeCommand struct {
Type string `json:"type"`
Data string `json:"data"`
}
-// BridgeResponse 桥接程序响应
+// BridgeResponse represents a bridge program response
type BridgeResponse struct {
Success bool `json:"success"`
Error string `json:"error"`
Data *BridgeTemperatureData `json:"data"`
}
-// RGBColor RGB 颜色
+// RGBColor represents an RGB color
type RGBColor struct {
R byte `json:"r"`
G byte `json:"g"`
B byte `json:"b"`
}
-// LightStripConfig 灯带配置
+// LightStripConfig represents light strip configuration
type LightStripConfig struct {
Mode string `json:"mode"` // off/smart_temp/static_single/static_multi/rotation/flowing/breathing
Speed string `json:"speed"` // fast/medium/slow
Brightness int `json:"brightness"` // 0-100
- Colors []RGBColor `json:"colors"` // 颜色列表
+ Colors []RGBColor `json:"colors"` // Color list
}
-// SmartControlConfig 智能控温配置
+// SmartControlConfig represents smart temperature control configuration
type SmartControlConfig struct {
- Enabled bool `json:"enabled"` // 智能耦合控制开关
- Learning bool `json:"learning"` // 学习开关
- TargetTemp int `json:"targetTemp"` // 目标温度(°C)
- Aggressiveness int `json:"aggressiveness"` // 响应激进度(1-10)
- Hysteresis int `json:"hysteresis"` // 滞回温差(°C)
- MinRPMChange int `json:"minRpmChange"` // 最小生效转速变化(RPM)
- RampUpLimit int `json:"rampUpLimit"` // 每次更新最大升速(RPM)
- RampDownLimit int `json:"rampDownLimit"` // 每次更新最大降速(RPM)
- LearnRate int `json:"learnRate"` // 学习速度(1-10)
- LearnWindow int `json:"learnWindow"` // 稳态学习窗口(采样点)
- LearnDelay int `json:"learnDelay"` // 学习延迟步数(处理热惯性)
- OverheatWeight int `json:"overheatWeight"` // 过热惩罚权重
- RPMDeltaWeight int `json:"rpmDeltaWeight"` // 转速变化惩罚权重
- NoiseWeight int `json:"noiseWeight"` // 高转速噪音惩罚权重
- TrendGain int `json:"trendGain"` // 温升趋势前馈增益
- MaxLearnOffset int `json:"maxLearnOffset"` // 学习偏移上限(RPM)
- LearnedOffsets []int `json:"learnedOffsets"` // 每个曲线点的学习偏移(RPM)
- LearnedOffsetsHeat []int `json:"learnedOffsetsHeat"` // 升温工况学习偏移(RPM)
- LearnedOffsetsCool []int `json:"learnedOffsetsCool"` // 降温工况学习偏移(RPM)
- LearnedRateHeat []int `json:"learnedRateHeat"` // 升温变化率学习偏置(分桶RPM)
- LearnedRateCool []int `json:"learnedRateCool"` // 降温变化率学习偏置(分桶RPM)
+ Enabled bool `json:"enabled"` // Smart coupled control switch
+ Learning bool `json:"learning"` // Learning switch
+ TargetTemp int `json:"targetTemp"` // Target temperature (°C)
+ Aggressiveness int `json:"aggressiveness"` // Response aggressiveness (1-10)
+ Hysteresis int `json:"hysteresis"` // Hysteresis temperature difference (°C)
+ MinRPMChange int `json:"minRpmChange"` // Minimum effective RPM change (RPM)
+ RampUpLimit int `json:"rampUpLimit"` // Maximum ramp-up per update (RPM)
+ RampDownLimit int `json:"rampDownLimit"` // Maximum ramp-down per update (RPM)
+ LearnRate int `json:"learnRate"` // Learning rate (1-10)
+ LearnWindow int `json:"learnWindow"` // Steady-state learning window (sample points)
+ LearnDelay int `json:"learnDelay"` // Learning delay steps (for thermal inertia)
+ OverheatWeight int `json:"overheatWeight"` // Overheat penalty weight
+ RPMDeltaWeight int `json:"rpmDeltaWeight"` // RPM change penalty weight
+ NoiseWeight int `json:"noiseWeight"` // High-RPM noise penalty weight
+ TrendGain int `json:"trendGain"` // Temperature rise trend feedforward gain
+ MaxLearnOffset int `json:"maxLearnOffset"` // Maximum learning offset (RPM)
+ LearnedOffsets []int `json:"learnedOffsets"` // Learning offset for each curve point (RPM)
+ LearnedOffsetsHeat []int `json:"learnedOffsetsHeat"` // Heating condition learning offsets (RPM)
+ LearnedOffsetsCool []int `json:"learnedOffsetsCool"` // Cooling condition learning offsets (RPM)
+ LearnedRateHeat []int `json:"learnedRateHeat"` // Heating rate learning biases (bucketed RPM)
+ LearnedRateCool []int `json:"learnedRateCool"` // Cooling rate learning biases (bucketed RPM)
}
-// AppConfig 应用配置
+// AppConfig represents the application configuration
type AppConfig struct {
- AutoControl bool `json:"autoControl"` // 智能变频开关
- ManualGearToggleHotkey string `json:"manualGearToggleHotkey"` // 切换手动挡位快捷键
- AutoControlToggleHotkey string `json:"autoControlToggleHotkey"` // 开关智能变频快捷键
- CurveProfileToggleHotkey string `json:"curveProfileToggleHotkey"` // 切换温控曲线方案快捷键
- ManualGearLevels map[string]string `json:"manualGearLevels"` // 每个大挡位记忆的小挡位(低中高)
- FanCurve []FanCurvePoint `json:"fanCurve"` // 风扇曲线
- FanCurveProfiles []FanCurveProfile `json:"fanCurveProfiles"` // 风扇曲线方案列表
- ActiveFanCurveProfileID string `json:"activeFanCurveProfileId"` // 当前激活曲线方案ID
- GearLight bool `json:"gearLight"` // 挡位灯
- PowerOnStart bool `json:"powerOnStart"` // 通电自启动
- WindowsAutoStart bool `json:"windowsAutoStart"` // Windows开机自启动
- SmartStartStop string `json:"smartStartStop"` // 智能启停
- Brightness int `json:"brightness"` // 亮度
- TempUpdateRate int `json:"tempUpdateRate"` // 温度更新频率(秒)
- TempSampleCount int `json:"tempSampleCount"` // 温度采样次数(用于平均)
- ConfigPath string `json:"configPath"` // 配置文件路径
- ManualGear string `json:"manualGear"` // 手动挡位设置
- ManualLevel string `json:"manualLevel"` // 手动挡位级别(低中高)
- DebugMode bool `json:"debugMode"` // 调试模式
- GuiMonitoring bool `json:"guiMonitoring"` // GUI监控开关
- CustomSpeedEnabled bool `json:"customSpeedEnabled"` // 自定义转速开关
- CustomSpeedRPM int `json:"customSpeedRPM"` // 自定义转速值(无上下限)
- IgnoreDeviceOnReconnect bool `json:"ignoreDeviceOnReconnect"` // 断连后忽略设备状态(保持APP配置)
- SmartControl SmartControlConfig `json:"smartControl"` // 学习型智能控温配置
- LightStrip LightStripConfig `json:"lightStrip"` // 灯带配置
+ AutoControl bool `json:"autoControl"` // Smart control switch
+ ManualGearToggleHotkey string `json:"manualGearToggleHotkey"` // Hotkey to toggle manual gear
+ AutoControlToggleHotkey string `json:"autoControlToggleHotkey"` // Hotkey to toggle smart control
+ CurveProfileToggleHotkey string `json:"curveProfileToggleHotkey"` // Hotkey to toggle fan curve profile
+ ManualGearLevels map[string]string `json:"manualGearLevels"` // Remembered sub-level for each main gear (low/mid/high)
+ FanCurve []FanCurvePoint `json:"fanCurve"` // Fan curve
+ FanCurveProfiles []FanCurveProfile `json:"fanCurveProfiles"` // Fan curve profile list
+ ActiveFanCurveProfileID string `json:"activeFanCurveProfileId"` // Currently active curve profile ID
+ GearLight bool `json:"gearLight"` // Gear indicator light
+ PowerOnStart bool `json:"powerOnStart"` // Start on power on
+ WindowsAutoStart bool `json:"windowsAutoStart"` // Windows auto-start on boot
+ SmartStartStop string `json:"smartStartStop"` // Smart start/stop
+ Brightness int `json:"brightness"` // Brightness
+ TempUpdateRate int `json:"tempUpdateRate"` // Temperature update rate (seconds)
+ TempSampleCount int `json:"tempSampleCount"` // Temperature sample count (for averaging)
+ ConfigPath string `json:"configPath"` // Configuration file path
+ ManualGear string `json:"manualGear"` // Manual gear setting
+ ManualLevel string `json:"manualLevel"` // Manual gear level (low/mid/high)
+ DebugMode bool `json:"debugMode"` // Debug mode
+ GuiMonitoring bool `json:"guiMonitoring"` // GUI monitoring switch
+ CustomSpeedEnabled bool `json:"customSpeedEnabled"` // Custom speed switch
+ CustomSpeedRPM int `json:"customSpeedRPM"` // Custom speed value (no upper/lower limit)
+ IgnoreDeviceOnReconnect bool `json:"ignoreDeviceOnReconnect"` // Ignore device state after disconnect (keep app config)
+ SmartControl SmartControlConfig `json:"smartControl"` // Learning-based smart temperature control config
+ LightStrip LightStripConfig `json:"lightStrip"` // Light strip configuration
}
-// GetDefaultLightStripConfig 获取默认灯带配置
+// GetDefaultLightStripConfig returns the default light strip configuration
func GetDefaultLightStripConfig() LightStripConfig {
return LightStripConfig{
Mode: "smart_temp",
@@ -159,7 +159,7 @@ func GetDefaultLightStripConfig() LightStripConfig {
}
}
-// GetDefaultSmartControlConfig 获取默认智能控温配置
+// GetDefaultSmartControlConfig returns the default smart temperature control configuration
func GetDefaultSmartControlConfig(curve []FanCurvePoint) SmartControlConfig {
offsets := make([]int, len(curve))
heatOffsets := make([]int, len(curve))
@@ -192,7 +192,7 @@ func GetDefaultSmartControlConfig(curve []FanCurvePoint) SmartControlConfig {
}
}
-// Logger 日志记录器接口
+// Logger is the logger interface
type Logger interface {
Info(format string, v ...any)
Error(format string, v ...any)
@@ -204,31 +204,31 @@ type Logger interface {
GetLogDir() string
}
-// GearCommands 预设挡位命令
+// GearCommands contains preset gear commands
var GearCommands = map[string][]GearCommand{
- "静音": {
- {"1挡低", []byte{0x5a, 0xa5, 0x26, 0x05, 0x00, 0x14, 0x05, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1300},
- {"1挡中", []byte{0x5a, 0xa5, 0x26, 0x05, 0x00, 0xa4, 0x06, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1700},
- {"1挡高", []byte{0x5a, 0xa5, 0x26, 0x05, 0x00, 0x6c, 0x07, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1900},
+ "Silent": {
+ {"Gear1-Low", []byte{0x5a, 0xa5, 0x26, 0x05, 0x00, 0x14, 0x05, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1300},
+ {"Gear1-Mid", []byte{0x5a, 0xa5, 0x26, 0x05, 0x00, 0xa4, 0x06, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1700},
+ {"Gear1-High", []byte{0x5a, 0xa5, 0x26, 0x05, 0x00, 0x6c, 0x07, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 1900},
},
- "标准": {
- {"2挡低", []byte{0x5a, 0xa5, 0x26, 0x05, 0x01, 0x34, 0x08, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2100},
- {"2挡中", []byte{0x5a, 0xa5, 0x26, 0x05, 0x01, 0x60, 0x09, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2310},
- {"2挡高", []byte{0x5a, 0xa5, 0x26, 0x05, 0x01, 0x8c, 0x0a, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2760},
+ "Standard": {
+ {"Gear2-Low", []byte{0x5a, 0xa5, 0x26, 0x05, 0x01, 0x34, 0x08, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2100},
+ {"Gear2-Mid", []byte{0x5a, 0xa5, 0x26, 0x05, 0x01, 0x60, 0x09, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2310},
+ {"Gear2-High", []byte{0x5a, 0xa5, 0x26, 0x05, 0x01, 0x8c, 0x0a, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2760},
},
- "强劲": {
- {"3挡低", []byte{0x5a, 0xa5, 0x26, 0x05, 0x02, 0xf0, 0x0a, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2800},
- {"3挡中", []byte{0x5a, 0xa5, 0x26, 0x05, 0x02, 0xb8, 0x0b, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3000},
- {"3挡高", []byte{0x5a, 0xa5, 0x26, 0x05, 0x02, 0xe4, 0x0c, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3300},
+ "Power": {
+ {"Gear3-Low", []byte{0x5a, 0xa5, 0x26, 0x05, 0x02, 0xf0, 0x0a, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 2800},
+ {"Gear3-Mid", []byte{0x5a, 0xa5, 0x26, 0x05, 0x02, 0xb8, 0x0b, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3000},
+ {"Gear3-High", []byte{0x5a, 0xa5, 0x26, 0x05, 0x02, 0xe4, 0x0c, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3300},
},
- "超频": {
- {"4挡低", []byte{0x5a, 0xa5, 0x26, 0x05, 0x03, 0xac, 0x0d, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3500},
- {"4挡中", []byte{0x5a, 0xa5, 0x26, 0x05, 0x03, 0x74, 0x0e, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3700},
- {"4挡高", []byte{0x5a, 0xa5, 0x26, 0x05, 0x03, 0xa0, 0x0f, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 4000},
+ "Overclock": {
+ {"Gear4-Low", []byte{0x5a, 0xa5, 0x26, 0x05, 0x03, 0xac, 0x0d, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3500},
+ {"Gear4-Mid", []byte{0x5a, 0xa5, 0x26, 0x05, 0x03, 0x74, 0x0e, 0xb0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 3700},
+ {"Gear4-High", []byte{0x5a, 0xa5, 0x26, 0x05, 0x03, 0xa0, 0x0f, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 4000},
},
}
-// GetDefaultFanCurve 获取默认风扇曲线
+// GetDefaultFanCurve returns the default fan curve
func GetDefaultFanCurve() []FanCurvePoint {
return []FanCurvePoint{
{Temperature: 30, RPM: 1000},
@@ -248,7 +248,7 @@ func GetDefaultFanCurve() []FanCurvePoint {
}
}
-// GetDefaultConfig 获取默认配置
+// GetDefaultConfig returns the default configuration
func GetDefaultConfig(isAutoStart bool) AppConfig {
defaultCurve := GetDefaultFanCurve()
@@ -258,14 +258,14 @@ func GetDefaultConfig(isAutoStart bool) AppConfig {
AutoControlToggleHotkey: "Ctrl+Alt+Shift+A",
CurveProfileToggleHotkey: "Ctrl+Alt+Shift+C",
ManualGearLevels: map[string]string{
- "静音": "中",
- "标准": "中",
- "强劲": "中",
- "超频": "中",
+ "Silent": "Mid",
+ "Standard": "Mid",
+ "Power": "Mid",
+ "Overclock": "Mid",
},
FanCurve: defaultCurve,
FanCurveProfiles: []FanCurveProfile{
- {ID: "default", Name: "默认", Curve: defaultCurve},
+ {ID: "default", Name: "Default", Curve: defaultCurve},
},
ActiveFanCurveProfileID: "default",
GearLight: true,
@@ -276,13 +276,13 @@ func GetDefaultConfig(isAutoStart bool) AppConfig {
TempUpdateRate: 2,
TempSampleCount: 1,
ConfigPath: "",
- ManualGear: "标准",
- ManualLevel: "中",
+ ManualGear: "Standard",
+ ManualLevel: "Mid",
DebugMode: false,
GuiMonitoring: true,
CustomSpeedEnabled: false,
CustomSpeedRPM: 2000,
- IgnoreDeviceOnReconnect: true, // 默认开启,防止断连后误判用户手动切换
+ IgnoreDeviceOnReconnect: true, // Enabled by default to prevent misjudging user manual switching after disconnect
SmartControl: GetDefaultSmartControlConfig(defaultCurve),
LightStrip: GetDefaultLightStripConfig(),
}
diff --git a/internal/version/version.go b/internal/version/version.go
index 5aa5254..6f7431c 100644
--- a/internal/version/version.go
+++ b/internal/version/version.go
@@ -4,12 +4,12 @@ import (
"strings"
)
-// BuildVersion 在编译时通过 ldflags 注入版本号
-// 示例: go build -ldflags "-X github.com/TIANLI0/BS2PRO-Controller/internal/version.BuildVersion=2.1.0"
+// BuildVersion is injected at compile time via ldflags
+// Example: go build -ldflags "-X github.com/TIANLI0/BS2PRO-Controller/internal/version.BuildVersion=2.1.0"
var BuildVersion = "dev"
-// Get 返回应用版本号
-// 优先使用编译时注入的版本号,如果未注入则返回 "dev"
+// Get returns the application version number
+// Prefers the version injected at compile time; returns "dev" if not injected
func Get() string {
if v := strings.TrimSpace(BuildVersion); v != "" {
return v
diff --git a/main.go b/main.go
index f4a6a5f..08acd35 100644
--- a/main.go
+++ b/main.go
@@ -31,10 +31,10 @@ func init() {
var wailsContext *context.Context
-// onSecondInstanceLaunch 当第二个实例启动时的回调函数
+// onSecondInstanceLaunch callback when a second instance is launched
func onSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
- println("检测到第二个实例启动,参数:", strings.Join(secondInstanceData.Args, ","))
- println("工作目录:", secondInstanceData.WorkingDirectory)
+ println("Second instance detected, args:", strings.Join(secondInstanceData.Args, ","))
+ println("Working directory:", secondInstanceData.WorkingDirectory)
if wailsContext != nil {
runtime.WindowUnminimise(*wailsContext)
@@ -49,75 +49,75 @@ func onSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) {
}
}
-// ensureCoreServiceRunning 确保核心服务正在运行
+// ensureCoreServiceRunning ensures the core service is running
func ensureCoreServiceRunning() bool {
- // 检测是否在 Wails 绑定生成模式下运行
+ // Detect if running in Wails binding generation mode
exePath, err := os.Executable()
if err == nil {
tempDir := os.TempDir()
if strings.HasPrefix(exePath, tempDir) {
- mainLogger.Info("检测到绑定生成模式,跳过核心服务启动")
+ mainLogger.Info("Binding generation mode detected, skipping core service startup")
return true
}
}
- // 检查核心服务是否已经在运行
+ // Check if core service is already running
if ipc.CheckCoreServiceRunning() {
- mainLogger.Info("核心服务已经在运行")
+ mainLogger.Info("Core service is already running")
return true
}
- mainLogger.Info("核心服务未运行,正在启动...")
+ mainLogger.Info("Core service is not running, starting...")
- // 获取核心服务路径
+ // Get core service path
if err != nil {
- mainLogger.Errorf("获取可执行文件路径失败: %v", err)
+ mainLogger.Errorf("Failed to get executable path: %v", err)
return false
}
exeDir := filepath.Dir(exePath)
corePath := filepath.Join(exeDir, "BS2PRO-Core.exe")
- // 检查核心服务是否存在
+ // Check if core service executable exists
if _, err := os.Stat(corePath); os.IsNotExist(err) {
- mainLogger.Errorf("核心服务程序不存在: %s", corePath)
+ mainLogger.Errorf("Core service executable not found: %s", corePath)
return false
}
- // 启动核心服务
+ // Start core service
cmd := exec.Command(corePath)
cmd.SysProcAttr = &syscall.SysProcAttr{
CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP | 0x08000000, // CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW
}
if err := cmd.Start(); err != nil {
- mainLogger.Errorf("启动核心服务失败: %v", err)
+ mainLogger.Errorf("Failed to start core service: %v", err)
return false
}
- mainLogger.Infof("核心服务已启动,PID: %d", cmd.Process.Pid)
+ mainLogger.Infof("Core service started, PID: %d", cmd.Process.Pid)
- // 释放进程句柄
+ // Release process handle
if cmd.Process != nil {
cmd.Process.Release()
}
- // 等待核心服务就绪
+ // Wait for core service to be ready
for range 50 {
time.Sleep(100 * time.Millisecond)
if ipc.CheckCoreServiceRunning() {
- mainLogger.Info("核心服务已就绪")
+ mainLogger.Info("Core service is ready")
return true
}
}
- mainLogger.Warn("等待核心服务就绪超时")
+ mainLogger.Warn("Timed out waiting for core service to be ready")
return false
}
func main() {
if !ensureCoreServiceRunning() {
- mainLogger.Warn("警告:无法启动核心服务,GUI 将以有限功能模式运行")
+ mainLogger.Warn("Warning: unable to start core service, GUI will run in limited functionality mode")
}
app := NewApp()
@@ -130,7 +130,7 @@ func main() {
}
}
- // 创建应用
+ // Create application
err := wails.Run(&options.App{
Title: "BS2PRO Controller",
Width: 1024,
diff --git a/scripts/ble_data.md b/scripts/ble_data.md
index 8cdfcaf..e5a2e47 100644
--- a/scripts/ble_data.md
+++ b/scripts/ble_data.md
@@ -1,12 +1,12 @@
-## 数据包控制记录
+## Data Packet Control Log
-### 通电自启动(开)
+### Power-on Auto-start (ON)
Frame 1132: 36 bytes on wire (288 bits), 36 bytes captured (288 bits) on interface TCP@127.0.0.1:24352, id 0
Section number: 1
Interface id: 0 (TCP@127.0.0.1:24352)
Encapsulation type: Bluetooth H4 with linux header (99)
- Arrival Time: Aug 15, 2025 21:33:47.128361000 中国标准时间
+ Arrival Time: Aug 15, 2025 21:33:47.128361000 China Standard Time
UTC Arrival Time: Aug 15, 2025 13:33:47.128361000 UTC
Epoch Arrival Time: 1755264827.128361000
[Time shift for this packet: 0.000000000 seconds]
@@ -28,13 +28,13 @@ Bluetooth HCI ACL Packet
Bluetooth L2CAP Protocol
Bluetooth Attribute Protocol
-### 通电自启动(关)
+### Power-on Auto-start (OFF)
Frame 1159: 36 bytes on wire (288 bits), 36 bytes captured (288 bits) on interface TCP@127.0.0.1:24352, id 0
Section number: 1
Interface id: 0 (TCP@127.0.0.1:24352)
Encapsulation type: Bluetooth H4 with linux header (99)
- Arrival Time: Aug 15, 2025 21:33:58.809403000 中国标准时间
+ Arrival Time: Aug 15, 2025 21:33:58.809403000 China Standard Time
UTC Arrival Time: Aug 15, 2025 13:33:58.809403000 UTC
Epoch Arrival Time: 1755264838.809403000
[Time shift for this packet: 0.000000000 seconds]
@@ -57,13 +57,13 @@ Bluetooth L2CAP Protocol
Bluetooth Attribute Protocol
-### 风扇转速设置(3300转)
+### Fan Speed Setting (3300 RPM)
Frame 2175: 36 bytes on wire (288 bits), 36 bytes captured (288 bits) on interface TCP@127.0.0.1:24352, id 0
Section number: 1
Interface id: 0 (TCP@127.0.0.1:24352)
Encapsulation type: Bluetooth H4 with linux header (99)
- Arrival Time: Aug 15, 2025 21:39:48.159705000 中国标准时间
+ Arrival Time: Aug 15, 2025 21:39:48.159705000 China Standard Time
UTC Arrival Time: Aug 15, 2025 13:39:48.159705000 UTC
Epoch Arrival Time: 1755265188.159705000
[Time shift for this packet: 0.000000000 seconds]
@@ -90,13 +90,13 @@ Bluetooth Attribute Protocol
Handle: 0x003c (Unknown)
Value: 5aa52104e40c150000000000000000000000000000000000
-### 风扇转速设置(1700转)
+### Fan Speed Setting (1700 RPM)
Frame 1771: 36 bytes on wire (288 bits), 36 bytes captured (288 bits) on interface TCP@127.0.0.1:24352, id 0
Section number: 1
Interface id: 0 (TCP@127.0.0.1:24352)
Encapsulation type: Bluetooth H4 with linux header (99)
- Arrival Time: Aug 15, 2025 21:38:01.018355000 中国标准时间
+ Arrival Time: Aug 15, 2025 21:38:01.018355000 China Standard Time
UTC Arrival Time: Aug 15, 2025 13:38:01.018355000 UTC
Epoch Arrival Time: 1755265081.018355000
[Time shift for this packet: 0.000000000 seconds]
diff --git a/scripts/ble_read.py b/scripts/ble_read.py
index 3136887..5e052b7 100644
--- a/scripts/ble_read.py
+++ b/scripts/ble_read.py
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
"""
-FlyDigi BS2PRO 蓝牙设备数据读取脚本
-监控设备的实时转速与目标转速
+FlyDigi BS2PRO Bluetooth Device Data Reading Script
+Monitors real-time RPM and target RPM of the device
"""
import asyncio
@@ -12,18 +12,18 @@
from bleak import BleakClient, BleakScanner
from bleak.backends.device import BLEDevice
-# 蓝牙厂商前缀映射
+# Bluetooth vendor prefix mapping
VENDOR_PREFIXES = {
- "e5:66:e5": "NanjingQinhe", # 根据数据包分析
+ "e5:66:e5": "NanjingQinhe", # Based on packet analysis
"00:00:00": "Unknown",
- # 可以添加更多厂商前缀
+ # More vendor prefixes can be added
}
-# 设备配置
+# Device configuration
TARGET_DEVICE_NAME = "FlyDigi BS2PRO"
-# 目标特性UUID - 从设备发现中找到的自定义特性
+# Target characteristic UUID - custom characteristic found during device discovery
TARGET_CHARACTERISTIC_UUID = "0000fff1-0000-1000-8000-00805f9b34fb"
-SCAN_TIMEOUT = 5.0 # 扫描超时时间(秒)
+SCAN_TIMEOUT = 5.0 # Scan timeout (seconds)
class BS2PROMonitor:
@@ -33,242 +33,242 @@ def __init__(self):
self.running = False
def get_vendor_from_mac(self, mac_address: str) -> str:
- """根据MAC地址前缀获取厂商信息"""
- mac_prefix = mac_address.lower()[:8] # 取前3字节
+ """Get vendor info from MAC address prefix"""
+ mac_prefix = mac_address.lower()[:8] # Take first 3 bytes
return VENDOR_PREFIXES.get(mac_prefix, "Unknown")
def parse_speed_data(self, data: bytearray) -> tuple:
"""
- 解析转速数据
- 根据数据包格式: 5aa5ef0b4a0705e40ce40cfb002b00000000000000000000
- 8-9字节: 目标转速 (大端 uint16)
- 10-11字节: 实际转速 (大端 uint16)
+ Parse speed data
+ Based on packet format: 5aa5ef0b4a0705e40ce40cfb002b00000000000000000000
+ Bytes 8-9: Target speed (big-endian uint16)
+ Bytes 10-11: Actual speed (big-endian uint16)
"""
if len(data) < 12:
- print(f"数据包长度不足: {len(data)} 字节 (需要至少12字节)")
+ print(f"Packet length insufficient: {len(data)} bytes (need at least 12 bytes)")
return None, None
- print(f"原始数据包: {data.hex()}")
+ print(f"Raw packet: {data.hex()}")
try:
- # 提取目标转速 (字节8-9, 大端序)
+ # Extract target speed (bytes 8-9, big-endian)
target_speed_bytes = data[8:10]
target_speed = struct.unpack(">H", target_speed_bytes)[0]
- print(f"目标转速字节 [8-9]: {target_speed_bytes.hex()} -> {target_speed}")
+ print(f"Target speed bytes [8-9]: {target_speed_bytes.hex()} -> {target_speed}")
- # 提取实际转速 (字节10-11, 大端序)
+ # Extract actual speed (bytes 10-11, big-endian)
actual_speed_bytes = data[10:12]
actual_speed = struct.unpack(">H", actual_speed_bytes)[0]
- print(f"实际转速字节 [10-11]: {actual_speed_bytes.hex()} -> {actual_speed}")
+ print(f"Actual speed bytes [10-11]: {actual_speed_bytes.hex()} -> {actual_speed}")
return target_speed, actual_speed
except struct.error as e:
- print(f"数据解析错误: {e}")
+ print(f"Data parsing error: {e}")
return None, None
def notification_handler(self, characteristic, data: bytearray):
- """处理接收到的通知数据"""
- # 获取特性的handle
- handle = characteristic.handle if hasattr(characteristic, "handle") else "未知"
+ """Handle received notification data"""
+ # Get characteristic handle
+ handle = characteristic.handle if hasattr(characteristic, "handle") else "Unknown"
uuid = (
str(characteristic.uuid).lower()
if hasattr(characteristic, "uuid")
- else "未知"
+ else "Unknown"
)
- print("\n=== 收到通知 ===")
- print(f"特性UUID: {uuid}")
+ print("\n=== Notification Received ===")
+ print(f"Characteristic UUID: {uuid}")
print(f"Handle: 0x{handle:04x}")
- print(f"数据长度: {len(data)} 字节")
- print(f"原始数据: {data.hex()}")
+ print(f"Data length: {len(data)} bytes")
+ print(f"Raw data: {data.hex()}")
- # 尝试解析转速数据(针对所有通知特性)
+ # Try to parse speed data (for all notification characteristics)
target_speed, actual_speed = self.parse_speed_data(data)
if target_speed is not None and actual_speed is not None:
- print(f"🎯 目标转速: {target_speed} RPM")
- print(f"⚡ 实际转速: {actual_speed} RPM")
- print(f"📊 转速差: {actual_speed - target_speed} RPM")
+ print(f"Target RPM: {target_speed} RPM")
+ print(f"Actual RPM: {actual_speed} RPM")
+ print(f"RPM Difference: {actual_speed - target_speed} RPM")
print("-" * 50)
else:
- print("❌ 无法解析为转速数据")
+ print("Unable to parse as speed data")
print("-" * 50)
async def scan_devices(self) -> Optional[BLEDevice]:
- """扫描蓝牙设备"""
- print(f"正在扫描蓝牙设备 ({SCAN_TIMEOUT}秒)...")
+ """Scan for Bluetooth devices"""
+ print(f"Scanning for Bluetooth devices ({SCAN_TIMEOUT} seconds)...")
- # 获取已发现的设备
+ # Get discovered devices
devices = await BleakScanner.discover(timeout=SCAN_TIMEOUT)
- print(f"\n发现 {len(devices)} 个蓝牙设备:")
+ print(f"\nDiscovered {len(devices)} Bluetooth devices:")
print("-" * 60)
target_device = None
for device in devices:
vendor = self.get_vendor_from_mac(device.address)
- device_name = device.name or "未知设备"
+ device_name = device.name or "Unknown Device"
- # 显示设备信息
- print(f"设备名称: {device_name}")
- print(f"MAC地址: {device.address}")
- print(f"厂商: {vendor}")
- # RSSI可能不是所有平台都有
+ # Display device information
+ print(f"Device Name: {device_name}")
+ print(f"MAC Address: {device.address}")
+ print(f"Vendor: {vendor}")
+ # RSSI may not be available on all platforms
try:
- print(f"RSSI: {getattr(device, 'rssi', '未知')} dBm")
+ print(f"RSSI: {getattr(device, 'rssi', 'Unknown')} dBm")
except Exception:
- print("RSSI: 未知")
+ print("RSSI: Unknown")
- # 检查是否为目标设备
+ # Check if this is the target device
if device.name == TARGET_DEVICE_NAME:
target_device = device
- print("*** 这是目标设备 ***")
+ print("*** This is the target device ***")
print("-" * 60)
if target_device:
- print(f"找到目标设备: {target_device.name} ({target_device.address})")
+ print(f"Target device found: {target_device.name} ({target_device.address})")
else:
- print(f"未找到目标设备: {TARGET_DEVICE_NAME}")
+ print(f"Target device not found: {TARGET_DEVICE_NAME}")
return target_device
async def connect_and_monitor(self, device: BLEDevice):
- """连接设备并开始监控"""
- print(f"正在连接到设备: {device.name} ({device.address})")
+ """Connect to device and start monitoring"""
+ print(f"Connecting to device: {device.name} ({device.address})")
try:
async with BleakClient(device) as client:
self.client = client
- print(f"成功连接到 {device.name}")
+ print(f"Successfully connected to {device.name}")
- # 获取设备服务信息
+ # Get device service information
services = client.services
service_count = (
len(services.services) if hasattr(services, "services") else 0
)
- print(f"设备服务数量: {service_count}")
+ print(f"Device service count: {service_count}")
- # 查找所有通知特性
+ # Find all notification characteristics
notification_chars = []
target_char = None
for service in services:
- print(f"\n服务: {service.uuid}")
+ print(f"\nService: {service.uuid}")
for char in service.characteristics:
print(
- f" 特性: {char.uuid} (Handle: 0x{char.handle:04x}) 属性: {char.properties}"
+ f" Characteristic: {char.uuid} (Handle: 0x{char.handle:04x}) Properties: {char.properties}"
)
if "notify" in char.properties:
print(
- f" *** 找到通知特性: {char.uuid} (Handle: 0x{char.handle:04x}) ***"
+ f" *** Found notification characteristic: {char.uuid} (Handle: 0x{char.handle:04x}) ***"
)
notification_chars.append(char)
- # 优先使用目标UUID的特性
+ # Prefer the target UUID characteristic
if (
str(char.uuid).lower()
== TARGET_CHARACTERISTIC_UUID.lower()
):
target_char = char
- print(" *** 这是目标通知特性 ***")
+ print(" *** This is the target notification characteristic ***")
if notification_chars:
- # 使用目标特性,如果没有则使用第一个可用的
+ # Use target characteristic, or fall back to the first available one
selected_char = (
target_char if target_char else notification_chars[0]
)
print(
- f"\n选择监听特性: {selected_char.uuid} (Handle: 0x{selected_char.handle:04x})"
+ f"\nSelected monitoring characteristic: {selected_char.uuid} (Handle: 0x{selected_char.handle:04x})"
)
await client.start_notify(selected_char, self.notification_handler)
- # 如果有多个通知特性,也尝试监听其他的
+ # If there are multiple notification characteristics, try monitoring others too
other_chars = [
char for char in notification_chars if char != selected_char
]
- for char in other_chars[:2]: # 最多监听额外2个特性
+ for char in other_chars[:2]: # Monitor up to 2 additional characteristics
try:
print(
- f"同时监听: {char.uuid} (Handle: 0x{char.handle:04x})"
+ f"Also monitoring: {char.uuid} (Handle: 0x{char.handle:04x})"
)
await client.start_notify(char, self.notification_handler)
except Exception as e:
- print(f"监听特性 {char.uuid} 失败: {e}")
+ print(f"Failed to monitor characteristic {char.uuid}: {e}")
- print("\n🚀 监控已启动,按 Ctrl+C 退出...")
- print("📡 等待转速数据...")
+ print("\nMonitoring started, press Ctrl+C to exit...")
+ print("Waiting for speed data...")
self.running = True
- # 保持连接并监听数据
+ # Keep connection alive and listen for data
while self.running:
await asyncio.sleep(1)
- # 停止所有通知
+ # Stop all notifications
for char in notification_chars:
try:
await client.stop_notify(char)
except Exception:
pass
- print("已停止监控")
+ print("Monitoring stopped")
else:
- print("未找到可用的通知特性")
- print("设备可能不支持通知功能")
+ print("No available notification characteristics found")
+ print("Device may not support notification functionality")
except Exception as e:
- print(f"连接错误: {e}")
+ print(f"Connection error: {e}")
def stop_monitoring(self):
- """停止监控"""
+ """Stop monitoring"""
self.running = False
- print("正在停止监控...")
+ print("Stopping monitoring...")
async def main():
- """主函数"""
+ """Main function"""
monitor = BS2PROMonitor()
- # 设置信号处理器
+ # Set up signal handlers
def signal_handler(signum, frame):
- print("\n接收到退出信号")
+ print("\nExit signal received")
monitor.stop_monitoring()
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
try:
- # 扫描设备
+ # Scan for devices
device = await monitor.scan_devices()
if device:
- # 连接并监控设备
+ # Connect and monitor device
await monitor.connect_and_monitor(device)
else:
- print("未找到目标设备,程序退出")
+ print("Target device not found, exiting")
return 1
except KeyboardInterrupt:
- print("\n程序被用户中断")
+ print("\nProgram interrupted by user")
except Exception as e:
- print(f"程序执行错误: {e}")
+ print(f"Program execution error: {e}")
return 1
return 0
if __name__ == "__main__":
- # 检查是否安装了bleak库
+ # Check if bleak library is installed
try:
import importlib.util
if importlib.util.find_spec("bleak") is None:
raise ImportError
- print("Bleak 库已安装")
+ print("Bleak library is installed")
except ImportError:
- print("错误: 未安装bleak库")
- print("请运行: pip install bleak")
+ print("Error: bleak library is not installed")
+ print("Please run: pip install bleak")
sys.exit(1)
- # 运行主程序
+ # Run main program
exit_code = asyncio.run(main())
sys.exit(exit_code)
diff --git a/scripts/ble_write.py b/scripts/ble_write.py
index 9566b7c..96350ff 100644
--- a/scripts/ble_write.py
+++ b/scripts/ble_write.py
@@ -6,7 +6,7 @@
from winrt.windows.storage.streams import DataWriter, Buffer
from winrt.windows.devices.enumeration import DeviceInformation
-# 添加 HID 支持
+# Add HID support
try:
import hid
@@ -14,43 +14,43 @@
from hid_controller import BS2PROHIDController
except ImportError:
HID_AVAILABLE = False
- print("⚠️ hidapi 未安装,HID 模式不可用。运行 'pip install hidapi' 安装。")
+ print("Warning: hidapi not installed, HID mode unavailable. Run 'pip install hidapi' to install.")
async def find_paired_ble_devices():
- """查找所有已配对的 BLE 设备"""
+ """Find all paired BLE devices"""
try:
- # 使用设备枚举器查找所有 BLE 设备
+ # Use device enumerator to find all BLE devices
device_selector = BluetoothLEDevice.get_device_selector()
devices = await DeviceInformation.find_all_async()
- print(f"找到 {len(devices)} 个 BLE 设备:")
+ print(f"Found {len(devices)} BLE devices:")
for device in devices:
- if device.name: # 只显示有名称的设备
- print(f"设备名称: {device.name}")
- print(f"设备ID: {device.id}")
- print(f"是否已启用: {device.is_enabled}")
- print(f"是否已配对: {device.pairing.is_paired}")
+ if device.name: # Only show devices with names
+ print(f"Device Name: {device.name}")
+ print(f"Device ID: {device.id}")
+ print(f"Enabled: {device.is_enabled}")
+ print(f"Paired: {device.pairing.is_paired}")
print("---")
- # 如果找到 BS2PRO 设备
+ # If BS2PRO device is found
if "BS2PRO" in device.name.upper():
- print(f"✓ 找到目标设备: {device.name}")
+ print(f"Found target device: {device.name}")
return device.id
return None
except Exception as e:
- print(f"枚举设备错误: {e}")
+ print(f"Device enumeration error: {e}")
return None
def analyze_characteristic_properties(properties):
- """分析特征值属性"""
+ """Analyze characteristic properties"""
prop_list = []
- # 使用 GattCharacteristicProperties 枚举值
+ # Use GattCharacteristicProperties enum values
if properties & GattCharacteristicProperties.READ:
prop_list.append("READ")
if properties & GattCharacteristicProperties.WRITE:
@@ -72,13 +72,13 @@ def analyze_characteristic_properties(properties):
def get_service_description(uuid_str):
- """获取标准服务描述"""
+ """Get standard service description"""
standard_services = {}
return standard_services.get(uuid_str.lower(), "Unknown Service")
def format_gatt_status(code: int) -> str:
- """格式化 GATT 状态码"""
+ """Format GATT status code"""
mapping = {
0: "Success",
1: "Unreachable",
@@ -89,30 +89,30 @@ def format_gatt_status(code: int) -> str:
async def discover_services_and_characteristics(device):
- """发现设备的所有服务和特征值"""
+ """Discover all services and characteristics of the device"""
try:
- print(f"\n🔍 正在分析设备: {device.name}")
- print(f"设备地址: {hex(device.bluetooth_address)}")
+ print(f"\nAnalyzing device: {device.name}")
+ print(f"Device address: {hex(device.bluetooth_address)}")
print("=" * 60)
- # 获取GATT服务
+ # Get GATT services
gatt_result = await device.get_gatt_services_async()
if gatt_result.status != 0:
- print(f"获取服务失败,状态码: {gatt_result.status}")
+ print(f"Failed to get services, status code: {gatt_result.status}")
return
services = gatt_result.services
- print(f"📋 找到 {len(services)} 个服务:\n")
+ print(f"Found {len(services)} services:\n")
for i, service in enumerate(services, 1):
service_uuid = str(service.uuid)
service_desc = get_service_description(service_uuid)
- print(f"🔧 服务 {i}: {service_desc}")
+ print(f"Service {i}: {service_desc}")
print(f" UUID: {service_uuid}")
- # 获取特征值(尝试使用未缓存模式)
+ # Get characteristics (try uncached mode)
try:
char_result = await service.get_characteristics_async(
BluetoothCacheMode.UNCACHED
@@ -122,7 +122,7 @@ async def discover_services_and_characteristics(device):
if char_result.status == 0:
characteristics = char_result.characteristics
- print(f" 📊 特征值数量: {len(characteristics)}")
+ print(f" Characteristic count: {len(characteristics)}")
for j, char in enumerate(characteristics, 1):
char_uuid = str(char.uuid)
@@ -130,25 +130,25 @@ async def discover_services_and_characteristics(device):
char.characteristic_properties
)
- print(f" 📌 特征值 {j}:")
+ print(f" Characteristic {j}:")
print(f" UUID: {char_uuid}")
- print(f" 属性: {', '.join(properties)}")
+ print(f" Properties: {', '.join(properties)}")
- # 标明读写能力
+ # Indicate read/write capabilities
capabilities = []
if "READ" in properties:
- capabilities.append("✅ 可读")
+ capabilities.append("Readable")
if "WRITE" in properties or "WRITE_NO_RESPONSE" in properties:
- capabilities.append("✏️ 可写")
+ capabilities.append("Writable")
if "NOTIFY" in properties:
- capabilities.append("🔔 可通知")
+ capabilities.append("Notifiable")
if "INDICATE" in properties:
- capabilities.append("📢 可指示")
+ capabilities.append("Indicatable")
if capabilities:
- print(f" 功能: {' | '.join(capabilities)}")
+ print(f" Capabilities: {' | '.join(capabilities)}")
- # 如果支持读取,尝试读取描述符
+ # If readable, try to read descriptors
try:
descriptors_result = await char.get_descriptors_async()
if (
@@ -156,7 +156,7 @@ async def discover_services_and_characteristics(device):
and len(descriptors_result.descriptors) > 0
):
print(
- f" 描述符: {len(descriptors_result.descriptors)} 个"
+ f" Descriptors: {len(descriptors_result.descriptors)}"
)
except: # noqa: E722
pass
@@ -164,107 +164,107 @@ async def discover_services_and_characteristics(device):
print()
else:
status_desc = format_gatt_status(char_result.status)
- print(f" ❌ 无法获取特征值,状态: {status_desc}")
+ print(f" Unable to get characteristics, status: {status_desc}")
- # 如果是 HID 服务且访问被拒绝,提示使用 HID 模式
+ # If this is an HID service and access is denied, suggest using HID mode
if (
service_uuid == "00001812-0000-1000-8000-00805f9b34fb"
and char_result.status == 3
and HID_AVAILABLE
):
- print(f" 💡 这是 HID 服务,建议使用 HID 模式访问")
+ print(f" This is an HID service, it is recommended to use HID mode for access")
print("-" * 50)
except Exception as e:
- print(f"发现服务错误: {e}")
+ print(f"Service discovery error: {e}")
async def connect_to_paired_device():
- """连接到已配对的 BS2PRO 设备"""
+ """Connect to a paired BS2PRO device"""
try:
- # 方法1: 通过 MAC 地址连接 (修正MAC地址)
- mac_address = 0xE566E510CE04 # 移除末尾的5
+ # Method 1: Connect via MAC address (corrected MAC address)
+ mac_address = 0xE566E510CE04 # Removed trailing 5
try:
- print("🔗 尝试通过MAC地址连接...")
+ print("Attempting to connect via MAC address...")
ble_device = await BluetoothLEDevice.from_bluetooth_address_async(
mac_address
)
if ble_device is not None:
- print(f"✅ 通过MAC地址连接成功: {ble_device.name}")
+ print(f"Connected via MAC address successfully: {ble_device.name}")
await discover_services_and_characteristics(ble_device)
return ble_device
except Exception as mac_error:
- print(f"❌ MAC地址连接失败: {mac_error}")
+ print(f"MAC address connection failed: {mac_error}")
- # 方法2: 通过设备枚举查找
- print("🔍 尝试通过设备枚举查找...")
+ # Method 2: Find via device enumeration
+ print("Attempting to find via device enumeration...")
device_id = await find_paired_ble_devices()
if device_id:
ble_device = await BluetoothLEDevice.from_id_async(device_id)
if ble_device is None:
- print("❌ 无法创建设备对象")
+ print("Unable to create device object")
return None
- print(f"✅ 连接到设备: {ble_device.name}")
+ print(f"Connected to device: {ble_device.name}")
await discover_services_and_characteristics(ble_device)
return ble_device
else:
- print("❌ 未找到 BS2PRO 设备")
+ print("BS2PRO device not found")
return None
except Exception as e:
- print(f"❌ 连接错误: {e}")
+ print(f"Connection error: {e}")
return None
-# 使用示例
+# Usage example
async def main():
- """主函数"""
- print("🚀 开始搜索并分析 BS2PRO 设备...")
- print("\n选择连接模式:")
- print("1. BLE GATT 模式 (默认)")
+ """Main function"""
+ print("Starting search and analysis of BS2PRO device...")
+ print("\nSelect connection mode:")
+ print("1. BLE GATT Mode (default)")
if HID_AVAILABLE:
- print("2. HID 模式")
+ print("2. HID Mode")
- choice = input("\n请选择模式 (1/2): ").strip()
+ choice = input("\nPlease select mode (1/2): ").strip()
if choice == "2" and HID_AVAILABLE:
- # 使用 HID 模式
- print("\n🔧 使用 HID 模式连接...")
+ # Use HID mode
+ print("\nConnecting using HID mode...")
controller = BS2PROHIDController()
if controller.connect():
- print("\n🧪 执行 HID 通信测试...")
- # 这里可以添加具体的 HID 命令测试
+ print("\nRunning HID communication test...")
+ # Specific HID command tests can be added here
controller.disconnect()
return
- # 默认使用 BLE GATT 模式
- print("\n🔧 使用 BLE GATT 模式连接...")
+ # Default to BLE GATT mode
+ print("\nConnecting using BLE GATT mode...")
device = await connect_to_paired_device()
if device:
- print("\n✅ 设备分析完成!")
- print(f"设备名称: {device.name}")
- print(f"设备地址: {hex(device.bluetooth_address)}")
- print("\n💡 提示: 查看上方输出找到可写的特征值UUID用于发送数据")
+ print("\nDevice analysis complete!")
+ print(f"Device Name: {device.name}")
+ print(f"Device Address: {hex(device.bluetooth_address)}")
+ print("\nTip: Check the output above to find writable characteristic UUIDs for sending data")
if HID_AVAILABLE:
- print("💡 如需访问 HID 服务,请重新运行并选择 HID 模式")
+ print("Tip: To access HID services, re-run and select HID mode")
else:
- print("\n❌ 未能连接到设备")
- print("\n🔧 故障排除建议:")
- print("1. 确保设备已在Windows设置中配对")
- print("2. 确保设备处于开启状态")
- print("3. 尝试在蓝牙设置中断开并重新连接设备")
+ print("\nFailed to connect to device")
+ print("\nTroubleshooting suggestions:")
+ print("1. Make sure the device is paired in Windows settings")
+ print("2. Make sure the device is powered on")
+ print("3. Try disconnecting and reconnecting the device in Bluetooth settings")
if HID_AVAILABLE:
- print("4. 尝试使用 HID 模式连接")
+ print("4. Try connecting using HID mode")
if __name__ == "__main__":
diff --git a/scripts/hid_controller.go b/scripts/hid_controller.go
index ffdb4fe..3bc0e3f 100644
--- a/scripts/hid_controller.go
+++ b/scripts/hid_controller.go
@@ -8,79 +8,79 @@ import (
"github.com/sstallion/go-hid"
)
-// 风扇数据结构
+// Fan data structure
type FanData struct {
- ReportID uint8 // 报告ID
- MagicSync uint16 // 同步头 0x5AA5
- Command uint8 // 命令码
- Status uint8 // 状态字节
- GearSettings uint8 // 最高挡位和设置挡位
- CurrentMode uint8 // 当前模式
- Reserved1 uint8 // 预留字节
- CurrentRPM uint16 // 风扇实时转速
- TargetRPM uint16 // 风扇目标转速
- MaxGear uint8 // 最高挡位 (从GearSettings解析)
- SetGear uint8 // 设置挡位 (从GearSettings解析)
+ ReportID uint8 // Report ID
+ MagicSync uint16 // Sync header 0x5AA5
+ Command uint8 // Command code
+ Status uint8 // Status byte
+ GearSettings uint8 // Max gear and set gear
+ CurrentMode uint8 // Current mode
+ Reserved1 uint8 // Reserved byte
+ CurrentRPM uint16 // Fan real-time RPM
+ TargetRPM uint16 // Fan target RPM
+ MaxGear uint8 // Max gear (parsed from GearSettings)
+ SetGear uint8 // Set gear (parsed from GearSettings)
}
-// 解析挡位设置
+// Parse gear settings
func parseGearSettings(gearByte uint8) (maxGear, setGear string) {
maxGearCode := (gearByte >> 4) & 0x0F
setGearCode := gearByte & 0x0F
- // 模式映射: 2=标准, 4=强劲, 6=超频
+ // Mode mapping: 2=Standard, 4=Turbo, 6=Overclock
maxGearMap := map[uint8]string{
- 0x2: "标准",
- 0x4: "强劲",
- 0x6: "超频",
+ 0x2: "Standard",
+ 0x4: "Turbo",
+ 0x6: "Overclock",
}
- // 挡位映射: 8=静音, A=标准, C=强劲, E=超频
+ // Gear mapping: 8=Silent, A=Standard, C=Turbo, E=Overclock
setGearMap := map[uint8]string{
- 0x8: "静音",
- 0xA: "标准",
- 0xC: "强劲",
- 0xE: "超频",
+ 0x8: "Silent",
+ 0xA: "Standard",
+ 0xC: "Turbo",
+ 0xE: "Overclock",
}
if val, ok := maxGearMap[maxGearCode]; ok {
maxGear = val
} else {
- maxGear = fmt.Sprintf("未知(0x%X)", maxGearCode)
+ maxGear = fmt.Sprintf("Unknown(0x%X)", maxGearCode)
}
if val, ok := setGearMap[setGearCode]; ok {
setGear = val
} else {
- setGear = fmt.Sprintf("未知(0x%X)", setGearCode)
+ setGear = fmt.Sprintf("Unknown(0x%X)", setGearCode)
}
return
}
-// 解析工作模式
+// Parse work mode
func parseWorkMode(mode uint8) string {
switch mode {
case 0x04:
- return "挡位工作模式"
+ return "Gear Operating Mode"
case 0x05:
- return "自动模式(实时转速)"
+ return "Auto Mode (Real-time Speed)"
default:
- return fmt.Sprintf("未知模式(0x%02X)", mode)
+ return fmt.Sprintf("Unknown Mode(0x%02X)", mode)
}
}
-// 解析HID数据包
+// Parse HID data packet
func parseFanData(data []byte, length int) *FanData {
if length < 11 {
- fmt.Printf("数据包长度不足,需要至少11字节,实际: %d\n", length)
+ fmt.Printf("Packet length insufficient, need at least 11 bytes, actual: %d\n", length)
return nil
}
- // 检查同步头
+ // Check sync header
magic := binary.BigEndian.Uint16(data[1:3])
if magic != 0x5AA5 {
- fmt.Printf("同步头不匹配,期望: 0x5AA5, 实际: 0x%04X\n", magic)
+ fmt.Printf("Sync header mismatch, expected: 0x5AA5, actual: 0x%04X\n", magic)
return nil
}
@@ -94,7 +94,7 @@ func parseFanData(data []byte, length int) *FanData {
Reserved1: data[7],
}
- // 解析转速 (小端序)
+ // Parse RPM (little-endian)
if length >= 10 {
fanData.CurrentRPM = binary.LittleEndian.Uint16(data[8:10])
}
@@ -105,112 +105,112 @@ func parseFanData(data []byte, length int) *FanData {
return fanData
}
-// 显示风扇数据
+// Display fan data
func displayFanData(fanData *FanData) {
- fmt.Println("\n=== 风扇数据解析 ===")
- fmt.Printf("报告ID: 0x%02X\n", fanData.ReportID)
- fmt.Printf("同步头: 0x%04X\n", fanData.MagicSync)
- fmt.Printf("命令码: 0x%02X\n", fanData.Command)
- fmt.Printf("状态字节: 0x%02X\n", fanData.Status)
+ fmt.Println("\n=== Fan Data Parsed ===")
+ fmt.Printf("Report ID: 0x%02X\n", fanData.ReportID)
+ fmt.Printf("Sync Header: 0x%04X\n", fanData.MagicSync)
+ fmt.Printf("Command Code: 0x%02X\n", fanData.Command)
+ fmt.Printf("Status Byte: 0x%02X\n", fanData.Status)
maxGear, setGear := parseGearSettings(fanData.GearSettings)
- fmt.Printf("挡位设置: 0x%02X (最高挡位: %s, 设置挡位: %s)\n",
+ fmt.Printf("Gear Settings: 0x%02X (Max Gear: %s, Set Gear: %s)\n",
fanData.GearSettings, maxGear, setGear)
- fmt.Printf("当前模式: %s (0x%02X)\n", parseWorkMode(fanData.CurrentMode), fanData.CurrentMode)
- fmt.Printf("预留字节: 0x%02X\n", fanData.Reserved1)
- fmt.Printf("风扇实时转速: %d RPM\n", fanData.CurrentRPM)
- fmt.Printf("风扇目标转速: %d RPM\n", fanData.TargetRPM)
+ fmt.Printf("Current Mode: %s (0x%02X)\n", parseWorkMode(fanData.CurrentMode), fanData.CurrentMode)
+ fmt.Printf("Reserved Byte: 0x%02X\n", fanData.Reserved1)
+ fmt.Printf("Fan Real-time RPM: %d RPM\n", fanData.CurrentRPM)
+ fmt.Printf("Fan Target RPM: %d RPM\n", fanData.TargetRPM)
fmt.Println("==================")
}
func main() {
- fmt.Println("HID连接测试")
+ fmt.Println("HID Connection Test")
- // 初始化HID库
+ // Initialize HID library
err := hid.Init()
if err != nil {
- fmt.Printf("初始化HID库失败: %v\n", err)
+ fmt.Printf("Failed to initialize HID library: %v\n", err)
return
}
defer func() {
- // 清理HID库资源
+ // Clean up HID library resources
if err := hid.Exit(); err != nil {
- fmt.Printf("清理HID库失败: %v\n", err)
+ fmt.Printf("Failed to clean up HID library: %v\n", err)
}
}()
- // 目标设备的厂商ID和产品ID
- vendorID := uint16(0x37D7) // 厂商ID: 0x37D7 (corrected from 0x137D7)
- productID := uint16(0x1002) // 产品ID: 0x1002
+ // Target device vendor ID and product ID
+ vendorID := uint16(0x37D7) // Vendor ID: 0x37D7 (corrected from 0x137D7)
+ productID := uint16(0x1002) // Product ID: 0x1002
- fmt.Printf("正在连接设备 - 厂商ID: 0x%04X, 产品ID: 0x%04X\n", vendorID, productID)
+ fmt.Printf("Connecting to device - Vendor ID: 0x%04X, Product ID: 0x%04X\n", vendorID, productID)
- // 直接打开第一个匹配的设备,无需枚举
+ // Open the first matching device directly, no enumeration needed
device, err := hid.OpenFirst(vendorID, productID)
if err != nil {
- fmt.Printf("打开设备失败: %v\n", err)
+ fmt.Printf("Failed to open device: %v\n", err)
return
}
defer func() {
if err := device.Close(); err != nil {
- fmt.Printf("关闭设备失败: %v\n", err)
+ fmt.Printf("Failed to close device: %v\n", err)
}
}()
- fmt.Println("设备连接成功!")
+ fmt.Println("Device connected successfully!")
- // 获取设备信息
+ // Get device information
deviceInfo, err := device.GetDeviceInfo()
if err != nil {
- fmt.Printf("获取设备信息失败: %v\n", err)
+ fmt.Printf("Failed to get device info: %v\n", err)
} else {
- fmt.Printf("设备详细信息:\n")
- fmt.Printf(" 制造商字符串: %s\n", deviceInfo.MfrStr)
- fmt.Printf(" 产品字符串: %s\n", deviceInfo.ProductStr)
- fmt.Printf(" 序列号: %s\n", deviceInfo.SerialNbr)
- fmt.Printf(" 版本号: 0x%04X\n", deviceInfo.ReleaseNbr)
+ fmt.Printf("Device Details:\n")
+ fmt.Printf(" Manufacturer: %s\n", deviceInfo.MfrStr)
+ fmt.Printf(" Product: %s\n", deviceInfo.ProductStr)
+ fmt.Printf(" Serial Number: %s\n", deviceInfo.SerialNbr)
+ fmt.Printf(" Release Number: 0x%04X\n", deviceInfo.ReleaseNbr)
}
- // 尝试读取数据(非阻塞模式)
+ // Try to read data (non-blocking mode)
err = device.SetNonblock(true)
if err != nil {
- fmt.Printf("设置非阻塞模式失败: %v\n", err)
+ fmt.Printf("Failed to set non-blocking mode: %v\n", err)
}
- // 读取示例
+ // Read example
buffer := make([]byte, 64)
- fmt.Println("尝试读取数据(超时5秒)...")
+ fmt.Println("Attempting to read data (5 second timeout)...")
n, err := device.ReadWithTimeout(buffer, 5*time.Second)
if err != nil {
if err == hid.ErrTimeout {
- fmt.Println("读取超时,设备可能没有发送数据")
+ fmt.Println("Read timed out, device may not be sending data")
} else {
- fmt.Printf("读取数据失败: %v\n", err)
+ fmt.Printf("Failed to read data: %v\n", err)
}
} else {
- fmt.Printf("读取到 %d 字节数据: ", n)
+ fmt.Printf("Read %d bytes of data: ", n)
for i := range n {
fmt.Printf("%02X ", buffer[i])
}
fmt.Println()
- // 解析风扇数据
+ // Parse fan data
if fanData := parseFanData(buffer, n); fanData != nil {
displayFanData(fanData)
}
}
- // 发送数据示例(如果需要)
- // 第一个字节通常是报告ID,对于只支持单一报告的设备应为0
- // outputData := []byte{0x00, 0x01, 0x02, 0x03} // 示例数据
+ // Send data example (if needed)
+ // The first byte is usually the report ID; for devices supporting only a single report, it should be 0
+ // outputData := []byte{0x00, 0x01, 0x02, 0x03} // Example data
// n, err = device.Write(outputData)
// if err != nil {
- // fmt.Printf("发送数据失败: %v\n", err)
+ // fmt.Printf("Failed to send data: %v\n", err)
// } else {
- // fmt.Printf("成功发送 %d 字节数据\n", n)
+ // fmt.Printf("Successfully sent %d bytes of data\n", n)
// }
- fmt.Println("HID设备操作完成")
+ fmt.Println("HID device operation completed")
}
diff --git a/scripts/hid_controller.py b/scripts/hid_controller.py
index 8fbba0d..39572fa 100644
--- a/scripts/hid_controller.py
+++ b/scripts/hid_controller.py
@@ -4,7 +4,7 @@
class BS2PROHIDController:
- """BS2PRO HID 控制器"""
+ """BS2PRO HID Controller"""
def __init__(self):
self.device = None
@@ -12,36 +12,36 @@ def __init__(self):
self.product_id = None
def find_bs2pro_devices(self) -> List[Tuple[int, int, str]]:
- """查找所有可能的 BS2PRO HID 设备"""
+ """Find all possible BS2PRO HID devices"""
devices = []
- print("查找 BS2PRO HID 设备...")
+ print("Searching for BS2PRO HID devices...")
- # 添加已知的 BS2PRO 设备信息
+ # Add known BS2PRO device information
known_bs2pro_devices = [
(0x137D7, 0x1002, "FlyDigi BS2PRO"),
]
- # 枚举所有设备并查找匹配项
+ # Enumerate all devices and find matches
for device_info in hid.enumerate():
vendor_id = device_info.get("vendor_id", 0)
product_id = device_info.get("product_id", 0)
manufacturer = device_info.get("manufacturer_string", "")
product_name = device_info.get("product_string", "")
- # 首先检查已知设备
+ # First check known devices
for known_vid, known_pid, known_name in known_bs2pro_devices:
if vendor_id == known_vid and product_id == known_pid:
devices.append((vendor_id, product_id, known_name))
- print(f"找到已知 BS2PRO 设备:")
- print(f" 厂商ID: 0x{vendor_id:04X}")
- print(f" 产品ID: 0x{product_id:04X}")
- print(f" 制造商: {manufacturer}")
- print(f" 产品名: {product_name}")
+ print(f"Found known BS2PRO device:")
+ print(f" Vendor ID: 0x{vendor_id:04X}")
+ print(f" Product ID: 0x{product_id:04X}")
+ print(f" Manufacturer: {manufacturer}")
+ print(f" Product Name: {product_name}")
print("-" * 40)
continue
- # 扩展搜索条件
+ # Extended search criteria
search_terms = ["BS2PRO", "FLYDIGI", "FLY", "CONTROLLER", "GAMEPAD"]
is_match = False
@@ -53,7 +53,7 @@ def find_bs2pro_devices(self) -> List[Tuple[int, int, str]]:
is_match = True
break
- # 也检查常见的游戏手柄厂商ID
+ # Also check common gamepad vendor IDs
common_gamepad_vendors = [
0x2DC8, # 8BitDo
0x045E, # Microsoft
@@ -77,11 +77,11 @@ def find_bs2pro_devices(self) -> List[Tuple[int, int, str]]:
product_name or f"Unknown-{vendor_id:04X}:{product_id:04X}",
)
)
- print(f"找到潜在设备:")
- print(f" 厂商ID: 0x{vendor_id:04X}")
- print(f" 产品ID: 0x{product_id:04X}")
- print(f" 制造商: {manufacturer}")
- print(f" 产品名: {product_name}")
+ print(f"Found potential device:")
+ print(f" Vendor ID: 0x{vendor_id:04X}")
+ print(f" Product ID: 0x{product_id:04X}")
+ print(f" Manufacturer: {manufacturer}")
+ print(f" Product Name: {product_name}")
print("-" * 40)
return devices
@@ -89,54 +89,54 @@ def find_bs2pro_devices(self) -> List[Tuple[int, int, str]]:
def connect(
self, vendor_id: Optional[int] = None, product_id: Optional[int] = None
) -> bool:
- """连接到设备"""
+ """Connect to device"""
try:
if vendor_id is None or product_id is None:
- # 查找潜在的 BS2PRO 设备
+ # Find potential BS2PRO devices
devices = self.find_bs2pro_devices()
if not devices:
- print("未找到匹配的 HID 设备")
- print("\n提示: 手动指定厂商ID和产品ID")
- print(" 例如: controller.connect(0x1234, 0x5678)")
+ print("No matching HID devices found")
+ print("\nTip: Manually specify vendor ID and product ID")
+ print(" Example: controller.connect(0x1234, 0x5678)")
return False
- # 尝试连接找到的每个设备
+ # Try connecting to each found device
for vid, pid, name in devices:
- print(f"尝试连接到: {name} (0x{vid:04X}:0x{pid:04X})")
+ print(f"Attempting to connect to: {name} (0x{vid:04X}:0x{pid:04X})")
if self._try_connect(vid, pid):
return True
- print("所有潜在设备连接失败")
+ print("All potential device connections failed")
return False
else:
return self._try_connect(vendor_id, product_id)
except Exception as e:
- print(f"连接过程出错: {e}")
+ print(f"Connection error: {e}")
return False
def _try_connect(self, vendor_id: int, product_id: int) -> bool:
- """尝试连接到指定的设备"""
+ """Try to connect to a specified device"""
try:
self.device = hid.device()
self.device.open(vendor_id, product_id)
self.vendor_id = vendor_id
self.product_id = product_id
- # 获取设备信息
+ # Get device information
manufacturer = self.device.get_manufacturer_string() or "Unknown"
product = self.device.get_product_string() or "Unknown"
- print(f"连接成功!")
- print(f" 制造商: {manufacturer}")
- print(f" 产品: {product}")
- print(f" 厂商ID: 0x{vendor_id:04X}")
- print(f" 产品ID: 0x{product_id:04X}")
+ print(f"Connected successfully!")
+ print(f" Manufacturer: {manufacturer}")
+ print(f" Product: {product}")
+ print(f" Vendor ID: 0x{vendor_id:04X}")
+ print(f" Product ID: 0x{product_id:04X}")
return True
except Exception as e:
- print(f"连接到 0x{vendor_id:04X}:0x{product_id:04X} 失败: {e}")
+ print(f"Failed to connect to 0x{vendor_id:04X}:0x{product_id:04X}: {e}")
if self.device:
try:
self.device.close()
@@ -146,194 +146,194 @@ def _try_connect(self, vendor_id: int, product_id: int) -> bool:
return False
def send_feature_report(self, report_id: int, data: bytes) -> bool:
- """发送特性报告"""
+ """Send feature report"""
try:
if not self.device:
- print("设备未连接")
+ print("Device not connected")
return False
- # 构造报告(报告ID + 数据)
+ # Construct report (report ID + data)
report = bytes([report_id]) + data
result = self.device.send_feature_report(report)
- print(f"发送特性报告成功: 报告ID={report_id}, 长度={result}")
+ print(f"Feature report sent successfully: report_id={report_id}, length={result}")
return True
except Exception as e:
- print(f"发送特性报告失败: {e}")
+ print(f"Failed to send feature report: {e}")
return False
def get_feature_report(self, report_id: int, length: int = 64) -> Optional[bytes]:
- """获取特性报告"""
+ """Get feature report"""
try:
if not self.device:
- print("设备未连接")
+ print("Device not connected")
return None
report = self.device.get_feature_report(report_id, length)
- print(f"接收特性报告: 报告ID={report_id}, 数据={bytes(report).hex()}")
+ print(f"Feature report received: report_id={report_id}, data={bytes(report).hex()}")
return bytes(report)
except Exception as e:
- print(f"获取特性报告失败: {e}")
+ print(f"Failed to get feature report: {e}")
return None
def send_output_report(self, data: bytes) -> bool:
- """发送输出报告"""
+ """Send output report"""
try:
if not self.device:
- print("设备未连接")
+ print("Device not connected")
return False
result = self.device.write(data)
- print(f"发送输出报告成功: 长度={result}")
+ print(f"Output report sent successfully: length={result}")
return True
except Exception as e:
- print(f"发送输出报告失败: {e}")
+ print(f"Failed to send output report: {e}")
return False
def read_input_report(self, timeout: int = 1000) -> Optional[bytes]:
- """读取输入报告"""
+ """Read input report"""
try:
if not self.device:
- print("设备未连接")
+ print("Device not connected")
return None
- # 设置非阻塞模式
+ # Set non-blocking mode
self.device.set_nonblocking(True)
data = self.device.read(64, timeout)
if data:
- print(f"接收输入报告: 数据={bytes(data).hex()}")
+ print(f"Input report received: data={bytes(data).hex()}")
return bytes(data)
else:
- print("未接收到输入报告(超时或无数据)")
+ print("No input report received (timeout or no data)")
return None
except Exception as e:
- print(f"读取输入报告失败: {e}")
+ print(f"Failed to read input report: {e}")
return None
def send_hex_command(
self, hex_string: str, report_id: int = 0x02, padding_length: int = 23
) -> bool:
- """发送十六进制命令
+ """Send hex command
Args:
- hex_string: 十六进制字符串 (如: "5aa5410243")
- report_id: 报告ID
- padding_length: 总长度,不足时用0填充
+ hex_string: Hex string (e.g., "5aa5410243")
+ report_id: Report ID
+ padding_length: Total length, padded with zeros if insufficient
"""
try:
- # 去除空格并转换为bytes
+ # Remove spaces and convert to bytes
hex_string = hex_string.replace(" ", "").replace("0x", "")
payload = bytes.fromhex(hex_string)
- # 计算需要填充的长度(总长度 - 报告ID(1字节) - 有效载荷长度)
+ # Calculate padding length needed (total length - report ID (1 byte) - payload length)
padding_needed = padding_length - 1 - len(payload)
if padding_needed < 0:
print(
- f"警告: 命令长度({len(payload)})超过最大允许长度({padding_length-1})"
+ f"Warning: Command length ({len(payload)}) exceeds maximum allowed length ({padding_length-1})"
)
padding_needed = 0
padding = bytes(padding_needed)
command = bytes([report_id]) + payload + padding
- print(f"发送命令: {hex_string} (总长度: {len(command)})")
+ print(f"Sending command: {hex_string} (total length: {len(command)})")
return self.send_output_report(command)
except ValueError as e:
- print(f"十六进制格式错误: {e}")
+ print(f"Hex format error: {e}")
return False
except Exception as e:
- print(f"发送命令失败: {e}")
+ print(f"Failed to send command: {e}")
return False
def send_multiple_commands(self, commands: List[str], delay: float = 0.1) -> int:
- """发送多个命令
+ """Send multiple commands
Args:
- commands: 命令列表
- delay: 命令间延迟(秒)
+ commands: List of commands
+ delay: Delay between commands (seconds)
Returns:
- 成功发送的命令数量
+ Number of successfully sent commands
"""
success_count = 0
for i, cmd in enumerate(commands):
cmd = cmd.strip()
- if not cmd: # 跳过空行
+ if not cmd: # Skip empty lines
continue
- print(f"\n命令 {i+1}/{len(commands)}: {cmd}")
+ print(f"\nCommand {i+1}/{len(commands)}: {cmd}")
if self.send_hex_command(cmd):
success_count += 1
- # 命令间延迟
+ # Delay between commands
if delay > 0 and i < len(commands) - 1:
time.sleep(delay)
print(
- f"\n完成! 成功发送 {success_count}/{len([c for c in commands if c.strip()])} 个命令"
+ f"\nDone! Successfully sent {success_count}/{len([c for c in commands if c.strip()])} commands"
)
return success_count
def calculate_checksum(self, rpm: int) -> int:
- """计算转速指令的校验和"""
- # 构造前6个字节:5aa52104 + 转速的小端序字节
+ """Calculate checksum for speed command"""
+ # Construct first 6 bytes: 5aa52104 + speed in little-endian bytes
speed_bytes = rpm.to_bytes(2, "little")
- # 前6个字节
+ # First 6 bytes
byte0 = 0x5A
byte1 = 0xA5
byte2 = 0x21
byte3 = 0x04
- byte4 = speed_bytes[0] # 转速低字节
- byte5 = speed_bytes[1] # 转速高字节
+ byte4 = speed_bytes[0] # Speed low byte
+ byte5 = speed_bytes[1] # Speed high byte
- # 校验字节 = (前6字节的和 + 1) & 0xFF
+ # Checksum byte = (sum of first 6 bytes + 1) & 0xFF
checksum = (byte0 + byte1 + byte2 + byte3 + byte4 + byte5 + 1) & 0xFF
return checksum
def enter_realtime_speed_mode(self) -> bool:
- """进入实时转速更改模式"""
- print("进入实时转速更改模式...")
+ """Enter real-time speed change mode"""
+ print("Entering real-time speed change mode...")
return self.send_hex_command("5aa523022500000000000000000000000000000000000000")
def set_fan_speed(self, rpm: int) -> bool:
- """设置风扇转速
+ """Set fan speed
Args:
- rpm: 转速值 (建议范围: 1000-4000)
+ rpm: Speed value (recommended range: 1000-4000)
"""
if not 0 <= rpm <= 65535:
- print(f"转速值超出范围: {rpm} (有效范围: 0-65535)")
+ print(f"Speed value out of range: {rpm} (valid range: 0-65535)")
return False
- # 将转速转换为小端序字节
+ # Convert speed to little-endian bytes
speed_bytes = rpm.to_bytes(2, "little")
- # 计算校验和
+ # Calculate checksum
checksum = self.calculate_checksum(rpm)
- # 构造完整命令
+ # Construct full command
command = (
f"5aa52104{speed_bytes.hex()}{checksum:02x}00000000000000000000000000000000"
)
- print(f"设置风扇转速: {rpm} RPM")
- print(f"命令: {command}")
+ print(f"Setting fan speed: {rpm} RPM")
+ print(f"Command: {command}")
return self.send_hex_command(command)
def set_gear_position(self, gear: int, position: int) -> bool:
- """设置挡位
+ """Set gear position
Args:
- gear: 档位 (1-4)
- position: 档位内位置 (1-3)
+ gear: Gear level (1-4)
+ position: Position within gear (1-3)
"""
gear_positions = {
(1, 1): "5aa526050014054400000000000000000000000000000000",
@@ -351,36 +351,36 @@ def set_gear_position(self, gear: int, position: int) -> bool:
}
if (gear, position) not in gear_positions:
- print(f"无效的挡位设置: {gear}档位{position}")
- print("有效范围: 1-4档,每档1-3个位置")
+ print(f"Invalid gear setting: gear {gear} position {position}")
+ print("Valid range: gears 1-4, each with positions 1-3")
return False
command = gear_positions[(gear, position)]
- print(f"设置挡位: {gear}档位{position}")
+ print(f"Setting gear: gear {gear} position {position}")
return self.send_hex_command(command)
def set_gear_light(self, enabled: bool) -> bool:
- """设置挡位灯开关"""
+ """Set gear indicator light on/off"""
if enabled:
command = "5aa54803014c000000000000000000000000000000000000"
- print("开启挡位灯")
+ print("Turning on gear indicator light")
else:
command = "5aa54803004b000000000000000000000000000000000000"
- print("关闭挡位灯")
+ print("Turning off gear indicator light")
return self.send_hex_command(command)
def set_power_on_start(self, enabled: bool) -> bool:
- """设置通电自启动"""
+ """Set power-on auto-start"""
if enabled:
command = "5aa50c030211000000000000000000000000000000000000"
- print("开启通电自启动")
+ print("Enabling power-on auto-start")
else:
command = "5aa50c030110000000000000000000000000000000000000"
- print("关闭通电自启动")
+ print("Disabling power-on auto-start")
return self.send_hex_command(command)
def set_smart_start_stop(self, mode: str) -> bool:
- """设置智能启停
+ """Set smart start/stop
Args:
mode: 'off', 'immediate', 'delayed'
@@ -392,104 +392,104 @@ def set_smart_start_stop(self, mode: str) -> bool:
}
if mode not in commands:
- print(f"无效的智能启停模式: {mode}")
- print("有效模式: 'off', 'immediate', 'delayed'")
+ print(f"Invalid smart start/stop mode: {mode}")
+ print("Valid modes: 'off', 'immediate', 'delayed'")
return False
- print(f"设置智能启停: {mode}")
+ print(f"Setting smart start/stop: {mode}")
return self.send_hex_command(commands[mode])
def set_brightness(self, percentage: int) -> bool:
- """设置灯光亮度
+ """Set light brightness
Args:
- percentage: 亮度百分比 (0-100)
+ percentage: Brightness percentage (0-100)
"""
if percentage == 0:
command = "5aa5470d1c00ff00000000000000006f0000000000000000"
- print("设置亮度: 0%")
+ print("Setting brightness: 0%")
elif percentage == 100:
command = "5aa543024500000000000000000000000000000000000000"
- print("设置亮度: 100%")
+ print("Setting brightness: 100%")
else:
- print(f"当前仅支持0%和100%亮度设置")
+ print(f"Currently only 0% and 100% brightness settings are supported")
return False
return self.send_hex_command(command)
def disconnect(self):
- """断开连接"""
+ """Disconnect"""
if self.device:
try:
self.device.close()
except:
pass
self.device = None
- print("设备已断开连接")
+ print("Device disconnected")
def test_bs2pro_with_commands():
- """测试 BS2PRO 设备并发送自定义命令"""
+ """Test BS2PRO device and send custom commands"""
controller = BS2PROHIDController()
- print("连接到 BS2PRO 设备...")
+ print("Connecting to BS2PRO device...")
print("=" * 50)
- # 直接使用已知的厂商ID和产品ID
+ # Use known vendor ID and product ID directly
if not controller.connect(0x137D7, 0x1002):
- print("无法连接到 BS2PRO 设备")
+ print("Unable to connect to BS2PRO device")
return False
- print("\n开始发送命令...")
+ print("\nStarting to send commands...")
print("-" * 30)
- # 测试命令列表 - 每行一个命令
+ # Test command list - one command per line
test_commands = [
"5aa54803014b0",
]
- # 发送多个命令
+ # Send multiple commands
controller.send_multiple_commands(test_commands, delay=0.2)
- # 测试读取输入
- print("\n监听输入数据...")
- print(" (按住手柄按键可能会看到数据...)")
+ # Test reading input
+ print("\nListening for input data...")
+ print(" (Press and hold controller buttons to see data...)")
for i in range(3):
response = controller.read_input_report(1000)
if response and any(response):
- print(f" 输入 {i+1}: {response[:16].hex()}...")
+ print(f" Input {i+1}: {response[:16].hex()}...")
break
else:
- print(f" 输入 {i+1}: 无数据")
+ print(f" Input {i+1}: No data")
- print("\n测试完成!")
+ print("\nTest completed!")
controller.disconnect()
return True
def interactive_command_mode():
- """交互式命令模式"""
+ """Interactive command mode"""
controller = BS2PROHIDController()
- print("交互式命令模式")
+ print("Interactive Command Mode")
print("=" * 50)
if not controller.connect(0x137D7, 0x1002):
- print("无法连接到 BS2PRO 设备")
+ print("Unable to connect to BS2PRO device")
return
- print("\n使用说明:")
- print(" - 输入十六进制命令 (如: 5aa5410243)")
- print(" - 多行输入用回车分隔,空行结束")
- print(" - 输入 'quit' 或 'exit' 退出")
- print(" - 输入 'listen' 监听输入数据")
- print(" - 输入 'speed ' 设置转速 (如: speed 2000)")
- print(" - 输入 'gear <档位> <位置>' 设置挡位 (如: gear 2 3)")
+ print("\nUsage:")
+ print(" - Enter hex commands (e.g., 5aa5410243)")
+ print(" - Separate multiple lines with Enter, empty line to finish")
+ print(" - Enter 'quit' or 'exit' to quit")
+ print(" - Enter 'listen' to listen for input data")
+ print(" - Enter 'speed ' to set speed (e.g., speed 2000)")
+ print(" - Enter 'gear ' to set gear (e.g., gear 2 3)")
print("-" * 30)
while True:
try:
- print("\n请输入命令:")
+ print("\nEnter command:")
line = input().strip()
if not line:
@@ -499,11 +499,11 @@ def interactive_command_mode():
break
if line.lower() == "listen":
- print("监听模式 (5秒)...")
+ print("Listen mode (5 seconds)...")
for i in range(5):
response = controller.read_input_report(1000)
if response and any(response):
- print(f" 输入: {response[:16].hex()}...")
+ print(f" Input: {response[:16].hex()}...")
continue
if line.lower().startswith("speed "):
@@ -513,7 +513,7 @@ def interactive_command_mode():
time.sleep(0.1)
controller.set_fan_speed(rpm)
except (IndexError, ValueError):
- print("用法: speed ")
+ print("Usage: speed ")
continue
if line.lower().startswith("gear "):
@@ -523,27 +523,27 @@ def interactive_command_mode():
position = int(parts[2])
controller.set_gear_position(gear, position)
except (IndexError, ValueError):
- print("用法: gear <档位> <位置>")
+ print("Usage: gear ")
continue
- # 普通十六进制命令
+ # Regular hex command
controller.send_hex_command(line)
except KeyboardInterrupt:
- print("\n\n用户中断,退出...")
+ print("\n\nUser interrupted, exiting...")
break
except Exception as e:
- print(f"错误: {e}")
+ print(f"Error: {e}")
controller.disconnect()
if __name__ == "__main__":
- print("选择模式:")
- print("1. 测试预设命令")
- print("2. 交互式命令模式")
+ print("Select mode:")
+ print("1. Test preset commands")
+ print("2. Interactive command mode")
- choice = input("请选择 (1/2): ").strip()
+ choice = input("Please choose (1/2): ").strip()
if choice == "2":
interactive_command_mode()
diff --git a/scripts/hid_data.md b/scripts/hid_data.md
index 9dcdbdc..c737866 100644
--- a/scripts/hid_data.md
+++ b/scripts/hid_data.md
@@ -1,105 +1,105 @@
-## Hid 指令记录
+## HID Command Log
-关闭挡位灯:5aa54803004b000000000000000000000000000000000000
+Turn off gear indicator light: 5aa54803004b000000000000000000000000000000000000
-开启挡位灯:5aa54803014c000000000000000000000000000000000000
+Turn on gear indicator light: 5aa54803014c000000000000000000000000000000000000
-1挡低:5aa526050014054400000000000000000000000000000000
+Gear 1 Low: 5aa526050014054400000000000000000000000000000000
-1405:1300
+1405: 1300
-1档中:5aa5260500a406d500000000000000000000000000000000
+Gear 1 Medium: 5aa5260500a406d500000000000000000000000000000000
a406: 1700
-1档高:5aa52605006c079e00000000000000000000000000000000
+Gear 1 High: 5aa52605006c079e00000000000000000000000000000000
6c07: 1900
-2挡低:5aa526050134086800000000000000000000000000000000
+Gear 2 Low: 5aa526050134086800000000000000000000000000000000
3408: 2100
-2档中:5aa526050160099500000000000000000000000000000000
+Gear 2 Medium: 5aa526050160099500000000000000000000000000000000
6009: 2310
-2档高:5aa52605018c0ac200000000000000000000000000000000
+Gear 2 High: 5aa52605018c0ac200000000000000000000000000000000
8c0a: 2760
-3档低:5aa5260502f00a2700000000000000000000000000000000
+Gear 3 Low: 5aa5260502f00a2700000000000000000000000000000000
f00a: 2800
-3档中:5aa5260502b80bf000000000000000000000000000000000
+Gear 3 Medium: 5aa5260502b80bf000000000000000000000000000000000
b80b: 3000
-3档高:5aa5260502e40c1d00000000000000000000000000000000
+Gear 3 High: 5aa5260502e40c1d00000000000000000000000000000000
e40c: 3300
-4档低:5aa5260503ac0de700000000000000000000000000000000
+Gear 4 Low: 5aa5260503ac0de700000000000000000000000000000000
ac0d: 3500
-4档中:5aa5260503740eb000000000000000000000000000000000
+Gear 4 Medium: 5aa5260503740eb000000000000000000000000000000000
740e: 3700
-4档高:5aa5260503a00fdd00000000000000000000000000000000
+Gear 4 High: 5aa5260503a00fdd00000000000000000000000000000000
a00f: 4000
-1挡:静音
+Gear 1: Silent
-2挡:标准
+Gear 2: Standard
-3挡:强劲
+Gear 3: Turbo
-4挡:超频
+Gear 4: Overclock
-通电自启动开:
+Power-on auto-start ON:
5aa50c030211000000000000000000000000000000000000
-通电自启动关:
+Power-on auto-start OFF:
5aa50c030110000000000000000000000000000000000000
-智能启停关:
+Smart start/stop OFF:
5aa50d030010000000000000000000000000000000000000
-智能启停开(即时):5aa50d030111000000000000000000000000000000000000
+Smart start/stop ON (immediate): 5aa50d030111000000000000000000000000000000000000
-智能启停开(延时):
+Smart start/stop ON (delayed):
5aa50d030212000000000000000000000000000000000000
-灯光亮度:
+Light brightness:
-应用:
+Apply:
5aa543024500000000000000000000000000000000000000
5aa541024300000000000000000000000000000000000000
-0%:
+0%:
5aa5470d1c00ff00000000000000006f0000000000000000
-100%:
+100%:
5aa543024500000000000000000000000000000000000000
-## 风扇转速,即时调整
+## Fan Speed, Real-time Adjustment
-进入实时转速更改模式:
+Enter real-time speed change mode:
5aa523022500000000000000000000000000000000000000
-实时转速设置(例子):
+Real-time speed setting (examples):
5aa521048d0fc10000000000000000000000000000000000
8d0f: 3981
5aa52104e40c150000000000000000000000000000000000
@@ -107,39 +107,39 @@ e40c: 3300
5aa52104a406cf0000000000000000000000000000000000
a406: 1700
-校验 = (byte0 + byte1 + byte2 + byte3 + byte4 + byte5 + 1) & 0xFF
+Checksum = (byte0 + byte1 + byte2 + byte3 + byte4 + byte5 + 1) & 0xFF
-# 接受
-## 风扇挡位
-1:01 5A A5 EF 0B 68 04 05 80 0C 14 05 C7 00 D7 00 00 00 00 00 00 00 00 00 00
-2:01 5A A5 EF 0B 6A 04 05 D0 07 34 08 E4 00 64 00 00 00 00 00 00 00 00 00 00
-3:01 5A A5 EF 0B 6C 04 05 60 09 F0 0A 15 01 E8 00 00 00 00 00 00 00 00 00 00
-4:01 5A A5 EF 0B 6E 04 05 54 0B AC 0D 34 01 BE 00 00 00 00 00 00 00 00 00 00
+# Received
+## Fan Gear Positions
+1: 01 5A A5 EF 0B 68 04 05 80 0C 14 05 C7 00 D7 00 00 00 00 00 00 00 00 00 00
+2: 01 5A A5 EF 0B 6A 04 05 D0 07 34 08 E4 00 64 00 00 00 00 00 00 00 00 00 00
+3: 01 5A A5 EF 0B 6C 04 05 60 09 F0 0A 15 01 E8 00 00 00 00 00 00 00 00 00 00
+4: 01 5A A5 EF 0B 6E 04 05 54 0B AC 0D 34 01 BE 00 00 00 00 00 00 00 00 00 00
-0:01 5A A5 EF 0B 68 05 05 C4 09 8D 0F A1 01 77 00 00 00 00 00 00 00 00 00 00
+0: 01 5A A5 EF 0B 68 05 05 C4 09 8D 0F A1 01 77 00 00 00 00 00 00 00 00 00 00
--:01 5A A5 EF 0B 48 04 05 78 05 14 05 85 00 66 00 00 00 00 00 00 00 00 00 00
+-: 01 5A A5 EF 0B 48 04 05 78 05 14 05 85 00 66 00 00 00 00 00 00 00 00 00 00
Offset | Size | Name | Value | Note
---------------------------------------------------------------
-0 | 1 | Report ID | 0x01 | 报告类型
-1-2 | 2 | Magic / Sync | 0x5AA5 | 同步头,固定值
-3 | 1 | Command / Type | 0xEF | 可能是命令码,风扇监听主要命令码是 0xEF
-4 | 1 | Status / Mode | 0x0B | 状态字节?
-5 | 1 | 最高挡位 & 设置挡位 | 0x68 | 拆分,高四位表示最高挡位,低四位表示设置挡位,比如 0x68 = 最高挡位“标准” + 设置挡位“静音”(模式分 2 4 6,分别对应 标准 强劲 超频;挡位分 8 A C E,分别对应 静音 标准 强劲 超频)
-6 | 1 | 当前模式 | 0x05 | 当前模式,0x04 = 挡位工作模式;0x05 = 自动模式(实时转速模式)
-7 | 1 | 预留 | 0x05 | 未知
-8-9 | 2 | 风扇实时转速 | 0xc409 | 风扇转速,单位 RPM,小端序; 0x09c4 = 2500
-9-10 | 2 | 风扇目标转速 | 0x8d0f | 风扇目标转速,单位 RPM,小端序; 0x0f8d = 3981
-11-24 | 14 | 预留 | 0x00 | 未知
+0 | 1 | Report ID | 0x01 | Report type
+1-2 | 2 | Magic / Sync | 0x5AA5 | Sync header, fixed value
+3 | 1 | Command / Type | 0xEF | Possibly a command code; the primary command code for fan monitoring is 0xEF
+4 | 1 | Status / Mode | 0x0B | Status byte
+5 | 1 | Max Gear & Set Gear | 0x68 | Split: high nibble represents max gear, low nibble represents set gear. E.g., 0x68 = max gear "Standard" + set gear "Silent" (modes: 2=Standard, 4=Turbo, 6=Overclock; gears: 8=Silent, A=Standard, C=Turbo, E=Overclock)
+6 | 1 | Current Mode | 0x05 | Current mode: 0x04 = Gear operating mode; 0x05 = Auto mode (real-time speed mode)
+7 | 1 | Reserved | 0x05 | Unknown
+8-9 | 2 | Fan Real-time RPM | 0xc409 | Fan speed in RPM, little-endian; 0x09c4 = 2500
+9-10 | 2 | Fan Target RPM | 0x8d0f | Fan target speed in RPM, little-endian; 0x0f8d = 3981
+11-24 | 14 | Reserved | 0x00 | Unknown
-最低1000
+Minimum 1000
-最高挡位为标准时最高挡位为标准,转速上限为 2760
+When max gear is Standard, the speed upper limit is 2760
-最高挡位为强劲时最高挡位为强劲,转速上限为 3300
+When max gear is Turbo, the speed upper limit is 3300
-最高挡位为超频时最高挡位为超频,转速上限为 4000
+When max gear is Overclock, the speed upper limit is 4000
diff --git a/scripts/rpm_rgb_probe.py b/scripts/rpm_rgb_probe.py
index 8d4ce0f..41103b8 100644
--- a/scripts/rpm_rgb_probe.py
+++ b/scripts/rpm_rgb_probe.py
@@ -136,7 +136,7 @@ def write_outputs(rows: List[Dict[str, object]], output_prefix: Path) -> None:
writer.writeheader()
writer.writerows(rows)
- # 做一个“变化字节”总结,便于对照颜色/区间
+ # Create a "changed bytes" summary for comparing color/range
valid_hex = [bytes.fromhex(r["raw_hex"]) for r in rows if r["raw_hex"]] # type: ignore
changed_indices: List[int] = []
if valid_hex:
@@ -147,35 +147,35 @@ def write_outputs(rows: List[Dict[str, object]], output_prefix: Path) -> None:
changed_indices.append(idx)
lines = [
- "# RPM -> 状态回包探针结果",
+ "# RPM -> Status Response Probe Results",
"",
- "## 结论摘要",
- f"- 样本点数: {len(rows)}",
- f"- 有效 0xEF 报文点数: {sum(1 for r in rows if (r['samples'] or 0) > 0)}", # type: ignore
- f"- 回包变化字节索引: {', '.join(str(i) for i in changed_indices) if changed_indices else '无'}",
+ "## Summary",
+ f"- Sample points: {len(rows)}",
+ f"- Valid 0xEF report points: {sum(1 for r in rows if (r['samples'] or 0) > 0)}", # type: ignore
+ f"- Changed byte indices in response: {', '.join(str(i) for i in changed_indices) if changed_indices else 'None'}",
"",
- "## 逐点结果",
+ "## Per-point Results",
]
for r in rows:
lines.append(
- f"- 设定 {r['set_rpm']} RPM -> 实际中位 {r['realtime_rpm_median']} / 目标中位 {r['target_rpm_median']} / 样本 {r['samples']}"
+ f"- Set {r['set_rpm']} RPM -> Actual median {r['realtime_rpm_median']} / Target median {r['target_rpm_median']} / Samples {r['samples']}"
)
lines.extend(
[
"",
- "## 字段提示",
- "- `gear_mode` 为回包偏移 5(高/低半字节混合字段)",
- "- `work_mode` 为回包偏移 6(常见 0x04/0x05)",
- "- `raw_hex` 保留整包,便于后续对照颜色状态位",
+ "## Field Notes",
+ "- `gear_mode` is response offset 5 (high/low nibble mixed field)",
+ "- `work_mode` is response offset 6 (common values: 0x04/0x05)",
+ "- `raw_hex` preserves the full packet for later comparison with color status bits",
]
)
md_path.write_text("\n".join(lines), encoding="utf-8")
- print(f"已写入: {csv_path}")
- print(f"已写入: {md_path}")
+ print(f"Written: {csv_path}")
+ print(f"Written: {md_path}")
def parse_rpm_points(text: str) -> List[int]:
@@ -190,63 +190,63 @@ def parse_rpm_points(text: str) -> List[int]:
def main() -> int:
parser = argparse.ArgumentParser(
- description="探测 smart_temp 模式下 RPM 与回包状态变化关系"
+ description="Probe the relationship between RPM and response status changes in smart_temp mode"
)
parser.add_argument(
"--rpm-points",
default="1000,1400,1700,2000,2300,2600,2900,3200,3500,3800,4000",
- help="逗号分隔 RPM 点",
+ help="Comma-separated RPM points",
)
parser.add_argument(
"--settle-sec",
type=float,
default=2.5,
- help="每个 RPM 点设置后等待稳定时间(秒)",
+ help="Settling time after setting each RPM point (seconds)",
)
parser.add_argument(
"--sample-sec",
type=float,
default=3.0,
- help="每个 RPM 点抓包时长(秒)",
+ help="Packet capture duration per RPM point (seconds)",
)
parser.add_argument(
"--vid",
type=lambda x: int(x, 0),
default=0x137D7,
- help="设备 VID(支持 0x 前缀)",
+ help="Device VID (supports 0x prefix)",
)
parser.add_argument(
"--pid",
type=lambda x: int(x, 0),
default=0x1002,
- help="设备 PID(支持 0x 前缀)",
+ help="Device PID (supports 0x prefix)",
)
args = parser.parse_args()
rpm_points = parse_rpm_points(args.rpm_points)
if not rpm_points:
- print("RPM 点为空")
+ print("RPM points are empty")
return 1
controller = BS2PROHIDController()
if not controller.connect(args.vid, args.pid):
- print("连接设备失败")
+ print("Failed to connect to device")
return 1
try:
if not enter_smart_temp_mode(controller):
- print("进入 smart_temp 模式失败")
+ print("Failed to enter smart_temp mode")
return 1
rows: List[Dict[str, object]] = []
for rpm in rpm_points:
- print(f"\n=== 探测 RPM: {rpm} ===")
+ print(f"\n=== Probing RPM: {rpm} ===")
controller.enter_realtime_speed_mode()
time.sleep(0.1)
if not controller.set_fan_speed(rpm):
- print(f"设置 RPM {rpm} 失败,跳过")
+ print(f"Failed to set RPM {rpm}, skipping")
rows.append(
{
"set_rpm": rpm,
@@ -267,14 +267,14 @@ def main() -> int:
row = summarize_probe_result(rpm, reports)
rows.append(row)
print(
- f"样本={row['samples']} 实际中位={row['realtime_rpm_median']} 目标中位={row['target_rpm_median']}"
+ f"samples={row['samples']} actual_median={row['realtime_rpm_median']} target_median={row['target_rpm_median']}"
)
timestamp = dt.datetime.now().strftime("%Y%m%d_%H%M%S")
out_prefix = Path("ota") / f"rpm_rgb_probe_{timestamp}"
write_outputs(rows, out_prefix)
- print("\n完成。请把生成的 CSV/MD 发我,我帮你反推出 RPM->颜色区间表。")
+ print("\nDone. Please send me the generated CSV/MD so I can help you reverse-engineer the RPM->color range table.")
return 0
finally:
controller.disconnect()
diff --git a/scripts/temp/temp.go b/scripts/temp/temp.go
index 4b113de..9a4ecb2 100644
--- a/scripts/temp/temp.go
+++ b/scripts/temp/temp.go
@@ -12,54 +12,54 @@ import (
)
func main() {
- // 获取CPU信息
+ // Get CPU information
cpus, err := cpu.Info()
if err != nil {
fmt.Println("Error getting CPU info:", err)
return
}
- // 打印CPU信息
+ // Print CPU information
for _, cpu := range cpus {
fmt.Printf("CPU: %s\n", cpu.ModelName)
fmt.Printf("Core Count: %d\n", cpu.Cores)
fmt.Printf("MHz: %f\n", cpu.Mhz)
}
- // 获取CPU使用率
+ // Get CPU usage
cpuPercent, err := cpu.Percent(0, false)
if err == nil && len(cpuPercent) > 0 {
fmt.Printf("CPU Usage: %.2f%%\n", cpuPercent[0])
}
- // 获取主机信息(包括温度信息,如果可用)
+ // Get host information (including temperature info if available)
hostInfo, err := host.Info()
if err == nil {
fmt.Printf("Host: %s\n", hostInfo.Hostname)
- fmt.Printf("系统: %s\n", hostInfo.Platform)
- fmt.Printf("系统版本: %s\n", hostInfo.PlatformVersion)
+ fmt.Printf("OS: %s\n", hostInfo.Platform)
+ fmt.Printf("OS Version: %s\n", hostInfo.PlatformVersion)
}
- // 尝试获取传感器信息(可能需要管理员权限)
+ // Try to get sensor information (may require admin privileges)
fmt.Println("\n--- Sensor Information ---")
sensors, err := sensors.SensorsTemperatures()
if err != nil {
- fmt.Printf("获取传感器数据时出错: %v\n", err)
+ fmt.Printf("Error getting sensor data: %v\n", err)
} else {
- // 打印传感器信息
+ // Print sensor information
for _, sensor := range sensors {
fmt.Printf("Sensor: %s\n", sensor.SensorKey)
fmt.Printf("Temperature: %.2f°C\n", sensor.Temperature)
}
}
- // 尝试获取GPU信息
+ // Try to get GPU information
fmt.Println("\n--- GPU Information ---")
gpus, err := GetNvidiaGPUInfo()
if err != nil {
- fmt.Printf("获取GPU信息时出错: %v\n", err)
+ fmt.Printf("Error getting GPU info: %v\n", err)
} else {
- // 打印GPU信息
+ // Print GPU information
for _, gpu := range gpus {
fmt.Printf("GPU: %s\n", gpu.Name)
fmt.Printf("Temperature: %d°C\n", gpu.Temperature)
@@ -68,16 +68,16 @@ func main() {
}
-// GPUInfo 表示单个GPU的信息
+// GPUInfo represents information for a single GPU
type GPUInfo struct {
Name string `json:"name"`
- Temperature int `json:"temperature"` // 单位: °C
+ Temperature int `json:"temperature"` // Unit: °C
}
-// GetNvidiaGPUInfo 使用 nvidia-smi 获取 GPU 名称和温度
-// 返回 GPUInfo 切片,每个元素对应一个GPU
+// GetNvidiaGPUInfo uses nvidia-smi to get GPU name and temperature
+// Returns a slice of GPUInfo, each element corresponding to a GPU
func GetNvidiaGPUInfo() ([]GPUInfo, error) {
- // 使用 nvidia-smi 查询 GPU 名称和温度,CSV 格式输出
+ // Use nvidia-smi to query GPU name and temperature, CSV format output
cmd := exec.Command("nvidia-smi",
"--query-gpu=name,temperature.gpu",
"--format=csv,noheader,nounits")
@@ -96,7 +96,7 @@ func GetNvidiaGPUInfo() ([]GPUInfo, error) {
continue
}
- // 格式: "GPU Name", temperature
+ // Format: "GPU Name", temperature
parts := strings.Split(line, ",")
if len(parts) != 2 {
return nil, fmt.Errorf("unexpected nvidia-smi output format: %s", line)
diff --git a/wails.json b/wails.json
index b5b4f58..7b600a8 100644
--- a/wails.json
+++ b/wails.json
@@ -15,7 +15,7 @@
"productName": "BS2PRO-Controller",
"productVersion": "2.9.1",
"copyright": "Copyright © 2026 TIANLI0",
- "comments": "飞智空间站的BS2/BS2PRO替代控制器"
+ "comments": "Alternative controller for Flydigi Space Station BS2/BS2PRO"
},
"nsisType": "multiple"
}