# ui_main_window.py
# 在 ui_main_window.py 文件开头添加
import sys
from power.power_sync import PowerTableSynchronizer
import os
from PyQt6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
QLineEdit, QPushButton, QTextEdit, QFileDialog,
QComboBox, QGroupBox, QApplication, QMessageBox, QDialog, QCheckBox
)
from PyQt6.QtCore import QObject, pyqtSignal
import logging
from pathlib import Path
from utils import get_output_dir
print(f" 当前工作目录: {os.getcwd()}")
print(f" 文件所在目录: {Path(__file__).resolve()}")
from excel_to_clm import ExcelToCLMConverter
from channel.range_sync import CLMRangeSynchronizer # 使用统一模块
# ============= 日志重定向器 ==============
class LogEmitter(QObject):
log_signal = pyqtSignal(str)
# ============= 主窗口类 ==============
class CLMGeneratorUI(QWidget):
def __init__(self, generator=None):
super().__init__()
self.generator = generator or ExcelToCLMConverter()
self.log_emitter = LogEmitter()
self.log_emitter.log_signal.connect(self.update_log_area)
# 设置日志系统以捕获同步器输出
self._setup_logging_redirect()
self.setWindowTitle("Wi-Fi CLM Generator Tool v1.2")
self.resize(700, 600)
self.init_ui()
def _setup_logging_redirect(self):
"""仅将 channel.* 日志转发到 UI,不写文件"""
class QtStreamHandler(logging.Handler):
def __init__(self, emitter):
super().__init__()
self.emitter = emitter
self.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
def emit(self, record):
try:
msg = self.format(record)
if msg.strip():
self.emitter.log_signal.emit(msg.strip())
except Exception:
self.handleError(record)
logger = logging.getLogger("channel")
logger.setLevel(logging.INFO)
if logger.hasHandlers():
logger.handlers.clear()
handler = QtStreamHandler(self.log_emitter)
logger.addHandler(handler)
logger.propagate = False
class LogStream:
"""伪装成文件对象的日志流,用于 logging.Handler"""
def __init__(self, emitter):
self.emitter = emitter
def write(self, msg):
if msg.strip():
self.emitter.log_signal.emit(msg.strip())
def flush(self):
pass
def init_ui(self):
layout = QVBoxLayout()
# === 输入区:Excel & Config ===
input_group = QGroupBox("输入配置")
input_layout = QVBoxLayout()
# Excel 文件
excel_row = QHBoxLayout()
excel_row.addWidget(QLabel("Excel 文件:"))
self.excel_path = QLineEdit("input/Archer BE900US 2.xlsx")
self.btn_browse_excel = QPushButton(" 浏览...")
self.btn_browse_excel.clicked.connect(self.select_excel)
excel_row.addWidget(self.excel_path)
excel_row.addWidget(self.btn_browse_excel)
input_layout.addLayout(excel_row)
# Config 文件
config_row = QHBoxLayout()
config_row.addWidget(QLabel("Config 文件:"))
self.config_path = QLineEdit("config/config.json")
self.btn_browse_config = QPushButton(" 浏览...")
self.btn_browse_config.clicked.connect(self.select_config_file)
config_row.addWidget(self.config_path)
config_row.addWidget(self.btn_browse_config)
input_layout.addLayout(config_row)
# Locale
locale_row = QHBoxLayout()
locale_row.addWidget(QLabel("Locale ID:"))
self.locale_combo = QComboBox()
self.locale_combo.addItems(["US", "FCC", "EU", "JP", "CN"])
locale_row.addWidget(self.locale_combo)
input_layout.addLayout(locale_row)
input_group.setLayout(input_layout)
layout.addWidget(input_group)
# === CLM 同步区 ===
sync_group = QGroupBox("CLM 范围同步")
sync_layout = QVBoxLayout()
# C 文件
cfile_row = QHBoxLayout()
cfile_row.addWidget(QLabel("C 源文件:"))
self.c_file_path = QLineEdit("input/wlc_clm_data_6726b0.c")
self.btn_browse_cfile = QPushButton(" 浏览...")
self.btn_browse_cfile.clicked.connect(self.select_c_file)
cfile_row.addWidget(self.c_file_path)
cfile_row.addWidget(self.btn_browse_cfile)
sync_layout.addLayout(cfile_row)
sync_group.setLayout(sync_layout)
layout.addWidget(sync_group)
# === 操作流程按钮组 ===
btn_group = QGroupBox("操作流程控制")
btn_layout = QHBoxLayout()
self.btn_parse = QPushButton(" 解析 Excel")
self.btn_dry_run = QPushButton(" 预览同步")
self.btn_sync = QPushButton(" 执行同步")
self.btn_full_run = QPushButton(" 一键生成并同步")
self.btn_parse.clicked.connect(self.run_parse)
self.btn_dry_run.clicked.connect(lambda: self.run_sync(dry_run=True))
self.btn_sync.clicked.connect(lambda: self.run_sync(dry_run=False))
self.btn_full_run.clicked.connect(self.run_full_process)
btn_layout.addWidget(self.btn_parse)
btn_layout.addWidget(self.btn_dry_run)
btn_layout.addWidget(self.btn_sync)
btn_layout.addWidget(self.btn_full_run)
btn_group.setLayout(btn_layout)
layout.addWidget(btn_group)
# === 功率表同步区 ===
power_sync_group = QGroupBox("功率表 & 国家码同步")
power_sync_layout = QVBoxLayout()
# 添加预览模式开关
self.chk_dry_run = QCheckBox(" 启用预览模式(不修改文件)")
self.chk_dry_run.setChecked(True) # 默认开启预览,更安全
power_sync_layout.addWidget(self.chk_dry_run)
# Locale 枚举选择(可多选)
locale_power_row = QHBoxLayout()
locale_power_row.addWidget(QLabel("默认Locale:"))
self.power_locale_combo = QComboBox()
self.power_locale_combo.addItems(["DEFAULT", "FCC", "ETSI", "JP", "CN"])
locale_power_row.addWidget(self.power_locale_combo)
power_sync_layout.addLayout(locale_power_row)
# 执行按钮
self.btn_sync_power = QPushButton("⚡ 注入功率表与国家码")
self.btn_sync_power.clicked.connect(self.run_power_sync)
power_sync_layout.addWidget(self.btn_sync_power)
power_sync_group.setLayout(power_sync_layout)
layout.addWidget(power_sync_group)
# === 日志输出 ===
self.log_area = QTextEdit()
self.log_area.setReadOnly(True)
self.log_area.append(" CLM Generator 已启动,等待输入...")
layout.addWidget(self.log_area)
self.setLayout(layout)
def run_power_sync(self):
"""运行功率表同步:提取 tx_limit_table.c 中的 DEFAULT 数据,注入到目标 .c 文件"""
target_c_path = self.c_file_path.text().strip()
if not target_c_path:
self.log(" 未指定目标 C 文件,请先设置!")
return
generated_table_path = Path("output") / "tx_limit_table.c"
config_path = self.config_path.text().strip() or "config/config.json"
if not generated_table_path.exists():
self.log(f" 无法找到生成的功率表文件: {generated_table_path}")
self.log(" 请先点击【开始生成】以生成 tx_limit_table.c")
return
try:
self.log(" 正在读取生成的功率表数据...")
gen_content = generated_table_path.read_text(encoding='utf-8')
# 提取 DEFAULT 功率表(Base 和 HT)
base_block = PowerTableSynchronizer._extract_locale_block(gen_content, "DEFAULT")
ht_block = PowerTableSynchronizer._extract_locale_block(gen_content, "DEFAULT_HT")
if not base_block and not ht_block:
self.log(" 未提取到任何 DEFAULT 功率表数据(检查是否生成了正确的 Locale)")
return
# 构造 locale_entries
selected_locale_idx = self.power_locale_combo.currentText()
idx_name = f"LOCALE_2G_IDX_{selected_locale_idx}"
locale_entries = {
"locale_2g_idx": idx_name
}
# 构造 power_tables
prefix = "locales_"
suffix = "[MAX_STREAMS_SUPPORTED][MODULATION_MODES]"
power_tables = {}
if base_block:
key = "2g_base_DEFAULT"
declaration = (
f"static const unsigned char {prefix}2g_base_DEFAULT[] = {{\n {base_block}\n}}"
)
power_tables[key] = declaration
self.log(f" 准备注入 2G Base 表")
if ht_block:
key = "2g_ht_DEFAULT"
declaration = (
f"static const unsigned char {prefix}2g_ht_DEFAULT[] = {{\n {ht_block}\n}}"
)
power_tables[key] = declaration
self.log(f" 准备注入 HT 表")
# 构造 country_entries(示例)
country_entries = [
f'REGION("CN", {idx_name})',
f'REGION("US", {idx_name})',
f'REGION("EU", {idx_name})'
]
self.log(f" 将为 CN/US/EU 注入国家码映射至 {idx_name}")
# === 关键:从复选框获取 dry_run 状态 ===
dry_run = self.chk_dry_run.isChecked()
# 创建同步器并执行
synchronizer = PowerTableSynchronizer(
config_path=config_path,
dry_run=dry_run # 可改为 True 测试
)
self.log(" 开始注入功率表和国家码...")
synchronizer.sync_all(
locale_entries=locale_entries,
power_tables=power_tables,
country_entries=country_entries
)
self.log(" 功率表与国家码同步完成!")
QMessageBox.information(self, "成功", "功率表与国家码已成功注入目标C文件!")
self.log(f" 将为 CN/US/EU 注入国家码映射至 {idx_name}")
except Exception as e:
self.log(f" 功率表同步失败: {str(e)}")
import traceback
self.log(f" 错误详情:\n{traceback.format_exc()}")
QMessageBox.critical(self, "错误", f"同步过程出错:{str(e)}")
def run_parse(self):
"""解析 Excel 并生成 manifest.json,不清除 generator 实例"""
excel_path = self.excel_path.text().strip()
config_path = self.config_path.text().strip() or "config/config.json"
if not excel_path:
self.log(" 请先选择 Excel 文件!")
return
try:
# 复用已有 generator,仅更新参数
self.generator.config_file_path = config_path
self.generator.locale_display_name = self.locale_combo.currentText()
self.generator.input_file = excel_path
self.log(" 清理旧数据...")
self.generator.reset() # 统一调用 reset 方法
self.log(" 开始解析 Excel 文件...")
self.generator.parse_excel()
self.generator.generate_outputs(finalize_config=True)
num_ranges = len(self.generator.used_ranges)
manifest_path = self.generator.last_config
self.log(f" 成功生成 {num_ranges} 个 RANGE 宏 → {manifest_path}")
self.log(" 提示:现在可以点击【预览同步】查看对 .c 文件的影响")
except Exception as e:
self.log(f" 解析失败: {str(e)}")
import traceback
self.log(f" 错误详情:\n{traceback.format_exc()}")
def select_excel(self):
file, _ = QFileDialog.getOpenFileName(
self, "选择 Excel 文件", "", "Excel Files (*.xlsx *.xls)")
if file:
self.excel_path.setText(file)
def select_config_file(self):
file, _ = QFileDialog.getOpenFileName(
self, "选择配置文件", "config/", "JSON 配置文件 (*.json);;All Files (*)")
if file:
self.config_path.setText(file)
def select_c_file(self):
file, _ = QFileDialog.getOpenFileName(
self, "选择 C 源文件", "input/", "C Source Files (*.c)")
if file:
self.c_file_path.setText(file)
def log(self, msg):
"""发送日志消息到信号队列"""
self.log_emitter.log_signal.emit(msg)
def update_log_area(self, msg):
"""更新日志文本框"""
self.log_area.append(f"> {msg}")
def clear_log(self):
self.log_area.clear()
def show_preview(self):
try:
output_file = get_output_dir()
with open(output_file, "r", encoding="utf-8") as f:
content = f.read()
dialog = QDialog(self)
dialog.setWindowTitle(f"预览: {output_file}")
dialog.resize(700, 500)
layout = QVBoxLayout()
text_edit = QTextEdit()
text_edit.setReadOnly(True)
text_edit.setText(content[:2000] + "\n...(后续内容已截断)")
close_btn = QPushButton("关闭")
close_btn.clicked.connect(dialog.accept)
layout.addWidget(text_edit)
layout.addWidget(close_btn)
dialog.setLayout(layout)
dialog.exec()
except FileNotFoundError:
self.log(f" 无法预览: 文件未生成 '{output_file}',请先点击【开始生成】")
except Exception as e:
self.log(f" 预览失败: {str(e)}")
def run_sync(self, dry_run=False):
"""运行同步:使用 generator 生成的 manifest 路径自动同步到 C 文件"""
from pathlib import Path
import logging
from datetime import datetime
# 检查是否已生成 manifest
if not hasattr(self.generator, 'last_config') or not self.generator.last_config:
self.log(" 尚未生成任何 文件,请先点击【解析 Excel】")
return
# 默认使用自动生成的路径,允许用户手动覆盖
c_file_path = self.c_file_path.text().strip()
if not c_file_path:
self.log(" 未指定目标 C 文件路径,请在下方输入或选择")
return
# 验证文件存在性
if not Path(c_file_path).exists():
self.log(f" 目标 C 文件不存在: {c_file_path}")
return
# ========== 【新增】准备日志文件(仅在执行模式)==========
file_handler = None
log_file_path = None
if not dry_run: # 只有执行模式才记录到文件
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
log_dir = Path(get_output_dir()) / "log" # 先转成 Path
log_dir.mkdir(parents=True, exist_ok=True)
log_file_path = log_dir / f"range_sync_{timestamp}.log"
# 创建专用 FileHandler
file_handler = logging.FileHandler(log_file_path, encoding='utf-8')
file_handler.setFormatter(
logging.Formatter('%(asctime)s [%(levelname)s] %(message)s', datefmt='%H:%M:%S')
)
# 添加到 channel logger(与 UI 输出共存)
target_logger = logging.getLogger("channel")
target_logger.addHandler(file_handler)
self.log(f" 日志文件已创建: {log_file_path}")
try:
mode = "预览模式" if dry_run else "执行模式"
self.log(f" 开始同步... ({mode})")
self.log(f" 修改文件: {c_file_path}")
# 创建同步器实例
sync = CLMRangeSynchronizer(
c_file_path=c_file_path,
dry_run=dry_run
)
was_modified = sync.run()
if dry_run:
if was_modified:
self.log("发现需要更新的内容,确认无误后可点击【执行同步】")
else:
self.log("所有 RANGE 已存在,无需修改")
else:
if was_modified:
self.log(f"同步成功!已更新 {c_file_path}")
self.log(f"备份已保存为 {c_file_path}.bak")
else:
self.log("文件已是最新,无需修改")
except Exception as e:
self.log(f"同步失败: {str(e)}")
import traceback
tb_msg = traceback.format_exc()
self.log(f"错误详情:\n{tb_msg}")
# 即使出错也要确保 file_handler 正确移除
raise
finally:
# ========== 【关键】清理资源:移除并关闭 FileHandler ==========
if file_handler:
try:
# 确保缓冲区写入磁盘
file_handler.flush()
file_handler.close()
logging.getLogger("channel").removeHandler(file_handler)
self.log("日志文件已关闭")
except Exception as e:
print(f"Warning: failed to close file handler: {e}")
# 可选:在 UI 显示最终提示
if not dry_run and log_file_path:
self.log(f"完整日志已保存至:\n{log_file_path}")
def run_full_process(self):
"""一键完成:解析 Excel → 生成 manifest → 同步到 .c 文件"""
reply = QMessageBox.question(
self,
"确认执行",
"是否要执行【解析Excel + 更新C文件】全流程?\n\n"
"建议先使用【预览同步】检查变更。",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
)
if reply != QMessageBox.StandardButton.Yes:
return
self.run_parse()
if hasattr(self.generator, 'last_config') and self.generator.last_config:
self.run_sync(dry_run=False)
else:
self.log(" 解析失败,无法继续同步")
if __name__ == '__main__':
app = QApplication([])
try:
window = CLMGeneratorUI()
window.show()
sys.exit(app.exec())
except Exception as e:
msg = QMessageBox()
msg.setWindowTitle("严重错误")
msg.setText(f"程序启动失败:\n\n{type(e).__name__}: {e}\n\n详情查看日志或联系开发者。")
msg.setIcon(QMessageBox.Critical)
msg.exec()
print(f"Critical error: {e}")
为什么执行注入功率表不会生成log文件,下面是注入功率表的文件代码# power/power_sync.py
import os
import re
import shutil
from pathlib import Path
import logging
from typing import Dict, List
from datetime import datetime
import json
from utils import resource_path
# --- 全局日志配置 ---
PROJECT_ROOT = Path(__file__).parent.parent.resolve()
LOG_DIR = PROJECT_ROOT / "output" / "log"
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOG_DIR / f"power_sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
class PowerTableSynchronizer:
# ——————————————————————————
# 1. 初始化与配置加载
# ——————————————————————————
def __init__(self, config_path= "config/config.json", dry_run = False):
self.dry_run = dry_run
# === Step 1: 使用 resource_path 定位项目根目录或资源基础路径 ===
# 注意:以 resource_path 为基准
base_dir = resource_path(".")# 指向 _MEIPASS 或 当前目录
self.project_root = base_dir.resolve()
self.output_dir = self.project_root / "output"
self.log_dir = self.output_dir / "log"
self.log_dir.mkdir(parents=True, exist_ok=True)
# === Step 2: 配置文件路径处理 ===
self.config_file_path = resource_path(config_path)
logging.info(f"配置文件: {self.config_file_path}")
if not os.path.exists(self.config_file_path):
raise FileNotFoundError(f"配置文件不存在: {self.config_file_path}")
try:
with open(self.config_file_path, 'r', encoding='utf-8') as f:
self.config = json.load(f)
print(f"配置文件已加载: {self.config_file_path}")
except json.JSONDecodeError as e:
raise ValueError(f"配置文件格式错误,JSON 解析失败: {self.config_file_path}") from e
except Exception as e:
raise RuntimeError(f"读取配置文件时发生未知错误: {e}") from e
# === Step 3: C 文件路径处理 ===
rel_c_path = self.config.get("c_file_path", "input/wlc_clm_data_6726b0.c")
self.target_c_file = resource_path(rel_c_path)
if not os.path.exists(self.target_c_file):
raise FileNotFoundError(f"配置中指定的 C 源文件不存在: {self.target_c_file}")
print(f" 已定位目标 C 文件: {self.target_c_file}")
self.platform = self.config.get("platform_rules", {})
self.logger = logging.getLogger(__name__)
# ——————————————————————————
# 2. 主接口(核心入口放最前面)
# ——————————————————————————
def sync_all(self,
locale_entries: Dict[str, str],
power_tables: Dict[str, str],
country_entries: List[str]):
"""
一站式同步所有 CLM 内容
"""
logging.info(f"开始执行 CLM 数据同步任务...")
logging.info(f"目标文件: {self.target_c_file.name}")
logging.info(f"预览模式: {'是' if self.dry_run else '否'}")
try:
content = self.load_content()
content = self.sync_locales_enum(content, locale_entries)
content = self.sync_power_tables(content, power_tables)
content = self.sync_country_definitions(content, country_entries)
self.save_content(content)
logging.info(" 全部同步任务完成!")
except Exception as e:
logging.info(f" 同步过程中发生错误: {str(e)}")
raise
# ——————————————————————————
# 3. 核心同步流程(按执行顺序排列)
# ——————————————————————————
def load_content(self) -> str:
"""读取目标 C 文件内容"""
try:
content = self.target_c_file.read_text(encoding='utf-8')
logging.info(f" 已读取文件: {self.target_c_file.name} ({len(content)} 字符)")
return content
except Exception as e:
logging.info(f" 读取文件失败: {e}")
raise
def sync_locales_enum(self, content: str, new_entries: Dict[str, str]) -> str:
"""同步 enum locale_xxx_idx 区域"""
logging.info(" 开始同步 LOCALE ENUMS...")
try:
block, start, end = self._extract_section(content, "enums")
except ValueError:
return content
lines = [line.rstrip() for line in block.splitlines()]
output_lines = []
current_enum = None
modified = False
i = 0
while i < len(lines):
line = lines[i]
stripped = line.strip()
enum_match = re.match(r"enum\s+([a-zA-Z0-9_]+)", stripped)
if enum_match:
enum_name = enum_match.group(1)
pattern = self.platform.get("locale_enum", {}).get("pattern", r"locale_(\w+)_idx")
if re.fullmatch(pattern.replace("\\", ""), enum_name):
current_enum = enum_name
output_lines.append(line)
i += 1
continue
if stripped.endswith("};") and current_enum:
key = current_enum.lower()
if key in new_entries:
entry_code = new_entries[key]
last_line = output_lines[-1].rstrip()
if not (last_line.endswith(",") or last_line.endswith("{")):
output_lines[-1] = last_line + ","
output_lines.append(f" {entry_code},")
logging.info(f" 新增枚举: {key} → {entry_code}")
modified = True
output_lines.append(line)
current_enum = None
i += 1
continue
output_lines.append(line)
i += 1
if not modified:
logging.info(" 枚举区无变更")
return content
new_block = "\n".join(output_lines)
updated = self._replace_section(content, "enums", new_block)
logging.info(" 枚举区更新完成")
return updated
def sync_power_tables(self, content: str, tables: Dict[str, str]) -> str:
"""同步功率表数组"""
logging.info(" 开始同步 POWER TABLES...")
try:
block, start, end = self._extract_section(content, "power_tables")
except ValueError:
return content
prefix = self.platform["power_table"]["prefix"]
suffix = self.platform["power_table"]["suffix"].replace("[", "\\[").replace("]", "\\]")
array_pattern = r'static\s+const\s+unsigned\s+char\s+(' + prefix + r'[a-zA-Z0-9_]+)' + suffix + r'\s*=\s*\{[^}]*\}\s*;'
existing_names = set(re.findall(array_pattern, block, re.IGNORECASE))
all_arrays = []
for match in re.finditer(array_pattern, block, re.DOTALL | re.IGNORECASE):
all_arrays.append(match.group(0))
added = []
for key, declaration in tables.items():
if key not in existing_names:
all_arrays.append(declaration.strip() + ";")
added.append(key)
logging.info(f" 新增功率表: {prefix}{key}{suffix}")
if not added:
logging.info(" 功率表区无新增")
return content
new_block = "\n\n".join(all_arrays)
updated = self._replace_section(content, "power_tables", new_block)
logging.info(f" 功率表更新完成,新增: {', '.join(added)}")
return updated
def sync_country_definitions(self, content: str, country_entries: List[str]) -> str:
"""同步国家码定义 REGION(...)"""
logging.info(" 开始同步 COUNTRY DEFINITIONS...")
try:
block, start, end = self._extract_section(content, "country_definitions")
except ValueError:
return content
pattern = self.platform["country_definition"]["pattern"]
existing_lines = [line.strip() for line in block.split('\n') if line.strip()]
country_map = {}
for line in existing_lines:
cc_match = re.search(pattern, line)
if cc_match:
country_map[cc_match.group(1)] = line
new_entries = []
for entry in country_entries:
cc_match = re.search(pattern, entry)
if cc_match:
cc = cc_match.group(1)
if cc not in country_map:
new_entries.append(entry.strip())
logging.info(f" 新增国家码: {cc}")
if not new_entries:
logging.info(" 国家码无新增")
return content
all_lines = existing_lines + new_entries
new_block = "\n".join(all_lines)
updated = self._replace_section(content, "country_definitions", new_block)
logging.info(f" 国家码更新完成,新增 {len(new_entries)} 个")
return updated
# ——————————————————————————
# 4. 内部文本操作辅助方法
# ——————————————————————————
def _extract_section(self, content: str, section: str) -> tuple[str, int, int]:
"""提取指定区域内容"""
try:
anchors = self.platform["anchors"][section]
start_marker = anchors["begin"]
end_marker = anchors["end"]
except KeyError as e:
raise ValueError(f"配置错误:缺少锚点定义 {section}") from e
start_pos = content.find(start_marker)
end_pos = content.find(end_marker)
if start_pos == -1 or end_pos == -1:
missing = "起始标记" if start_pos == -1 else "结束标记"
msg = f"未找到区域 {section} 的 {missing}: {start_marker[:30]}..."
logging.warning(msg)
raise ValueError(msg)
inner_start = start_pos + len(start_marker)
inner_end = end_pos
extracted = content[inner_start:inner_end].strip()
logging.info(f" 提取区域 [{section}]: {len(extracted)} 字符")
return extracted, inner_start, inner_end
def _replace_section(self, content: str, section: str, new_content: str) -> str:
"""替换指定区域内容"""
try:
anchors = self.platform["anchors"][section]
start_marker = anchors["begin"]
end_marker = anchors["end"]
except KeyError as e:
raise ValueError(f"配置错误:无法替换区域 {section}") from e
start_pos = content.find(start_marker)
end_pos = content.find(end_marker)
if start_pos == -1 or end_pos == -1:
raise ValueError(f"无法替换区域 {section},缺失锚点")
return content[:start_pos + len(start_marker)] + "\n" + new_content + "\n" + content[end_pos:]
def save_content(self, content: str):
"""保存内容并创建备份"""
if self.dry_run:
logging.info(" 【预览模式】跳过实际写入")
return
try:
backup_path = self.target_c_file.with_suffix(self.target_c_file.suffix + ".bak")
shutil.copy2(self.target_c_file, backup_path)
logging.info(f" 已创建备份: {backup_path.name}")
with open(self.target_c_file, 'w', encoding='utf-8') as f:
f.write(content)
logging.info(f" 成功更新文件: {self.target_c_file.name}")
except Exception as e:
logging.error(f"写入文件失败: {e}")
raise
# ——————————————————————————
# 5. 工具函数(静态方法,独立于实例)
# ——————————————————————————
@staticmethod
def _extract_locale_block(content: str, locale_name: str) -> str | None:
"""
从 tx_limit_table.c 中提取 /* Locale NAME */ 对应的数据块
返回去注释后的干净数据字符串
"""
pattern = rf'/\*\s*Locale\s+{locale_name}\s*\*/\s*([^/]*)'
match = re.search(pattern, content, re.DOTALL)
if not match:
return None
block = match.group(1).strip()
block = re.sub(r'/\*.*?\*/', '', block) # 去除注释
block = re.sub(r'\s+', ' ', block).strip() # 归一化空白
return block
# ——————————————————————————
# 主函数:注入 DEFAULT 功率表
# ——————————————————————————
def main():
"""
主函数:注入 DEFAULT 功率表
使用项目根目录自动推导路径,不依赖当前工作目录
"""
import sys
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s [%(levelname)s] %(name)s: %(message)s',
handlers=[
logging.FileHandler(LOG_FILE, encoding='utf-8'),
logging.StreamHandler(sys.stdout)
],
force=True
)
log_level = "INFO"
logging.getLogger().setLevel(log_level)
project_root = Path(__file__).parent.parent # F:\issue
CONFIG_FILE = project_root / "config" / "config.json"
GENERATED_FILE = project_root / "output" / "tx_limit_table.c"
TARGET_C_FILE = project_root / "input" / "wlc_clm_data_6726b0.c"
# 检查必要文件
for path, desc in [(CONFIG_FILE, "配置文件"), (GENERATED_FILE, "生成数据文件"), (TARGET_C_FILE, "目标C文件")]:
if not path.exists():
print(f" {desc}不存在: {path}", file=sys.stderr)
sys.exit(1)
try:
gen_content = GENERATED_FILE.read_text(encoding='utf-8')
base_block = PowerTableSynchronizer._extract_locale_block(gen_content, "DEFAULT")
ht_block = PowerTableSynchronizer._extract_locale_block(gen_content, "DEFAULT_HT")
if not base_block and not ht_block:
raise ValueError("未提取到任何 DEFAULT 功率表数据")
locale_entries = {
"locale_2g_idx": "LOCALE_2G_IDX_DEFAULT"
}
power_tables = {}
if base_block:
power_tables["2g_base_DEFAULT"] = (
f"static const unsigned char locales_2g_base_DEFAULT[] = {{\n {base_block}\n}}"
)
if ht_block:
power_tables["2g_ht_DEFAULT"] = (
f"static const unsigned char locales_2g_ht_DEFAULT[] = {{\n {ht_block}\n}}"
)
country_entries = [
'REGION("CN", LOCALE_2G_IDX_DEFAULT)',
'REGION("US", LOCALE_2G_IDX_DEFAULT)',
]
synchronizer = PowerTableSynchronizer(
config_path=str(CONFIG_FILE),
dry_run=False
)
synchronizer.sync_all(
locale_entries=locale_entries,
power_tables=power_tables,
country_entries=country_entries
)
print(" 成功注入 DEFAULT 功率表!")
print(f" 详细日志已保存至: {LOG_FILE}")
except Exception as e:
print(f" 错误: {e}", file=sys.stderr)
sys.exit(1)
# ——————————————————————————
# 运行入口(可选)
# ——————————————————————————
if __name__ == "__main__":
main()
最新发布