2026-07-02 15:53:30 +08:00
|
|
|
|
# Ploss 解析器
|
|
|
|
|
|
|
|
|
|
|
|
> 所属模块:[解析器设计](./概览.md)
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 1. 日志格式
|
2026-07-03 18:06:01 +08:00
|
|
|
|
```
|
|
|
|
|
|
pow_loss = 3053, delta_p = 1950
|
|
|
|
|
|
FOD-> (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) (15) (16)
|
|
|
|
|
|
```
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 2. 字段定义
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
### 2.1 第一行字段
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
| 编号 | 字段名称 | 数据类型 | 说明 |
|
|
|
|
|
|
|------|----------|----------|------|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
| (A) | PowLoss | int | 功率损耗值 |
|
|
|
|
|
|
| (B) | DeltaP | int | 功率差值 |
|
|
|
|
|
|
|
|
|
|
|
|
### 2.2 第二行字段 (FOD->)
|
|
|
|
|
|
|
|
|
|
|
|
| 编号 | 字段名称 | 数据类型 | 说明 |
|
|
|
|
|
|
|------|----------|----------|------|
|
|
|
|
|
|
| (1) | Field1 | int | 待定义 |
|
|
|
|
|
|
| (2) | Field2 | int | 待定义 |
|
|
|
|
|
|
| (3) | Field3 | int | 待定义 |
|
|
|
|
|
|
| (4) | Field4 | int | 待定义 |
|
|
|
|
|
|
| (5) | Field5 | int | 待定义 |
|
|
|
|
|
|
| (6) | Field6 | int | 待定义 |
|
|
|
|
|
|
| (7) | Field7 | int | 待定义 |
|
|
|
|
|
|
| (8) | Field8 | int | 待定义 |
|
|
|
|
|
|
| (9) | Field9 | int | 待定义 |
|
|
|
|
|
|
| (10) | Field10 | int | 待定义 |
|
|
|
|
|
|
| (11) | Field11 | int | 待定义 |
|
|
|
|
|
|
| (12) | Field12 | int | 待定义 |
|
|
|
|
|
|
| (13) | Field13 | int | 待定义 |
|
|
|
|
|
|
| (14) | Field14 | int | 待定义 |
|
|
|
|
|
|
| (15) | Field15 | int | 待定义 |
|
|
|
|
|
|
| (16) | Field16 | int | 待定义 |
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 3. 正则表达式
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
日志由两行组成,需要分别匹配:
|
2026-07-02 18:13:56 +08:00
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
```csharp
|
|
|
|
|
|
// Parsers/PlossParser.cs
|
2026-07-03 18:06:01 +08:00
|
|
|
|
// 第一行格式: pow_loss = 3053, delta_p = 1950
|
|
|
|
|
|
private static readonly Regex HeaderPattern = new(
|
|
|
|
|
|
@"^pow_loss\s*=\s*(\d+),\s*delta_p\s*=\s*(\d+)$",
|
|
|
|
|
|
RegexOptions.Compiled
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 第二行格式: FOD-> 后跟16个数字(均支持负数)
|
|
|
|
|
|
private static readonly Regex FodPattern = new(
|
|
|
|
|
|
@"^FOD->\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)$",
|
2026-07-02 15:53:30 +08:00
|
|
|
|
RegexOptions.Compiled
|
|
|
|
|
|
);
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
> **说明**:所有16个字段均支持负数,使用 `-?\d+` 匹配。
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
**捕获组映射:**
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
**第一行 (HeaderPattern):**
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
| 捕获组 | 字段 | 类型转换 |
|
|
|
|
|
|
|--------|------|----------|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
| Group 1 | PowLoss | int.Parse() |
|
|
|
|
|
|
| Group 2 | DeltaP | int.Parse() |
|
|
|
|
|
|
|
|
|
|
|
|
**第二行 (FodPattern):**
|
|
|
|
|
|
|
|
|
|
|
|
| 捕获组 | 字段 | 类型转换 |
|
|
|
|
|
|
|--------|------|----------|
|
|
|
|
|
|
| Group 1 | Field1 | int.Parse() |
|
|
|
|
|
|
| Group 2 | Field2 | int.Parse() |
|
|
|
|
|
|
| Group 3 | Field3 | int.Parse() |
|
|
|
|
|
|
| Group 4 | Field4 | int.Parse() |
|
|
|
|
|
|
| Group 5 | Field5 | int.Parse() |
|
|
|
|
|
|
| Group 6 | Field6 | int.Parse() |
|
|
|
|
|
|
| Group 7 | Field7 | int.Parse() |
|
|
|
|
|
|
| Group 8 | Field8 | int.Parse() |
|
|
|
|
|
|
| Group 9 | Field9 | int.Parse() |
|
|
|
|
|
|
| Group 10 | Field10 | int.Parse() |
|
|
|
|
|
|
| Group 11 | Field11 | int.Parse() |
|
|
|
|
|
|
| Group 12 | Field12 | int.Parse() |
|
|
|
|
|
|
| Group 13 | Field13 | int.Parse() |
|
|
|
|
|
|
| Group 14 | Field14 | int.Parse() |
|
|
|
|
|
|
| Group 15 | Field15 | int.Parse() |
|
|
|
|
|
|
| Group 16 | Field16 | int.Parse() |
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 4. 解析器实现
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// Parsers/PlossParser.cs
|
|
|
|
|
|
using System.Text.RegularExpressions;
|
2026-08-13 13:29:56 +08:00
|
|
|
|
using Gpulse.WCT.DataAnalyzer.Models;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
2026-08-13 13:29:56 +08:00
|
|
|
|
namespace Gpulse.WCT.DataAnalyzer.Parsers;
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
|
|
|
|
|
public class PlossParser : IParser<PlossRecord>
|
|
|
|
|
|
{
|
2026-07-03 18:06:01 +08:00
|
|
|
|
private static readonly Regex HeaderPattern = new(
|
|
|
|
|
|
@"^pow_loss\s*=\s*(\d+),\s*delta_p\s*=\s*(\d+)$",
|
2026-07-02 15:53:30 +08:00
|
|
|
|
RegexOptions.Compiled
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
private static readonly Regex FodPattern = new(
|
|
|
|
|
|
@"^FOD->\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)$",
|
|
|
|
|
|
RegexOptions.Compiled
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
public bool CanParse(string line) => HeaderPattern.IsMatch(line) || FodPattern.IsMatch(line);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
|
2026-07-02 18:13:56 +08:00
|
|
|
|
public ParseResult<PlossRecord> Parse(string line, Guid scenarioId)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
{
|
2026-07-03 18:06:01 +08:00
|
|
|
|
// 尝试匹配第一行
|
|
|
|
|
|
var headerMatch = HeaderPattern.Match(line);
|
|
|
|
|
|
if (headerMatch.Success)
|
2026-07-02 15:53:30 +08:00
|
|
|
|
{
|
2026-07-03 18:06:01 +08:00
|
|
|
|
return ParseHeaderLine(headerMatch, scenarioId);
|
2026-07-02 15:53:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
// 尝试匹配第二行
|
|
|
|
|
|
var fodMatch = FodPattern.Match(line);
|
|
|
|
|
|
if (fodMatch.Success)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ParseFodLine(fodMatch, scenarioId);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return ParseResult<PlossRecord>.Failure(
|
|
|
|
|
|
"FORMAT_MISMATCH",
|
|
|
|
|
|
"Line does not match Ploss pattern"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private ParseResult<PlossRecord> ParseHeaderLine(Match match, Guid scenarioId)
|
|
|
|
|
|
{
|
2026-07-02 15:53:30 +08:00
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var record = new PlossRecord
|
|
|
|
|
|
{
|
2026-07-02 18:13:56 +08:00
|
|
|
|
ScenarioId = scenarioId,
|
2026-07-03 18:06:01 +08:00
|
|
|
|
PowLoss = int.Parse(match.Groups[1].Value),
|
|
|
|
|
|
DeltaP = int.Parse(match.Groups[2].Value),
|
|
|
|
|
|
IsHeaderLine = true
|
2026-07-02 15:53:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
return ParseResult<PlossRecord>.Success(record);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ParseResult<PlossRecord>.Failure(
|
|
|
|
|
|
"TYPE_CONVERSION",
|
2026-07-03 18:06:01 +08:00
|
|
|
|
$"Failed to convert header field: {ex.Message}"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private ParseResult<PlossRecord> ParseFodLine(Match match, Guid scenarioId)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var record = new PlossRecord
|
|
|
|
|
|
{
|
|
|
|
|
|
ScenarioId = scenarioId,
|
|
|
|
|
|
Field1 = int.Parse(match.Groups[1].Value),
|
|
|
|
|
|
Field2 = int.Parse(match.Groups[2].Value),
|
|
|
|
|
|
Field3 = int.Parse(match.Groups[3].Value),
|
|
|
|
|
|
Field4 = int.Parse(match.Groups[4].Value),
|
|
|
|
|
|
Field5 = int.Parse(match.Groups[5].Value),
|
|
|
|
|
|
Field6 = int.Parse(match.Groups[6].Value),
|
|
|
|
|
|
Field7 = int.Parse(match.Groups[7].Value),
|
|
|
|
|
|
Field8 = int.Parse(match.Groups[8].Value),
|
|
|
|
|
|
Field9 = int.Parse(match.Groups[9].Value),
|
|
|
|
|
|
Field10 = int.Parse(match.Groups[10].Value),
|
|
|
|
|
|
Field11 = int.Parse(match.Groups[11].Value),
|
|
|
|
|
|
Field12 = int.Parse(match.Groups[12].Value),
|
|
|
|
|
|
Field13 = int.Parse(match.Groups[13].Value),
|
|
|
|
|
|
Field14 = int.Parse(match.Groups[14].Value),
|
|
|
|
|
|
Field15 = int.Parse(match.Groups[15].Value),
|
|
|
|
|
|
Field16 = int.Parse(match.Groups[16].Value),
|
|
|
|
|
|
IsHeaderLine = false
|
|
|
|
|
|
};
|
|
|
|
|
|
return ParseResult<PlossRecord>.Success(record);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ParseResult<PlossRecord>.Failure(
|
|
|
|
|
|
"TYPE_CONVERSION",
|
|
|
|
|
|
$"Failed to convert FOD field: {ex.Message}"
|
2026-07-02 15:53:30 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-07-03 18:06:01 +08:00
|
|
|
|
> **注意**:实际实现中,两行日志需要合并为一条完整记录。可以使用 `PlossRecordMerger` 或在业务逻辑层处理合并。
|
|
|
|
|
|
|
2026-07-02 15:53:30 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 5. 核心判定逻辑
|
|
|
|
|
|
|
|
|
|
|
|
### 5.1 FOD 触发条件
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
if (Ploss > Threshold) → 计数器累积
|
|
|
|
|
|
if (Count > Limit) → 触发 FOD 报警,停止充电
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 5.2 安全余量计算
|
|
|
|
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
|
// 派生属性(不落库)
|
|
|
|
|
|
Margin = Threshold - Ploss
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
| Margin 状态 | 含义 |
|
|
|
|
|
|
|-------------|------|
|
|
|
|
|
|
| Margin > 0 | 安全,损耗低于阈值 |
|
|
|
|
|
|
| Margin < 0 | 危险,损耗超过阈值,可能触发 FOD |
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 6. 动态阈值表
|
|
|
|
|
|
|
|
|
|
|
|
阈值根据接收功率动态查表:
|
|
|
|
|
|
|
|
|
|
|
|
| 接收功率区间 (Rx Power) | 阈值 (Threshold) | 备注 |
|
|
|
|
|
|
|------------------------|------------------|------|
|
|
|
|
|
|
| 0 ~ 5W (0-5000 mW) | 350 mW | 低功率最敏感 |
|
|
|
|
|
|
| 5 ~ 10W (5000-10000 mW) | 500 mW | |
|
|
|
|
|
|
| 10 ~ 15W (10000-15000 mW) | 750 mW | |
|
|
|
|
|
|
| 15 ~ 30W (15000-30000 mW) | 1000 mW | 示例 Log 处于此区间 |
|
|
|
|
|
|
| 30 ~ 50W (30000-50000 mW) | 1250 mW | 高功率允许更多损耗 |
|