修复N卡DDC通道限制导致不能识别超过15字节,导致可能漏判问题
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using WinISP;
|
||||||
|
|
||||||
namespace MTK
|
namespace MTK
|
||||||
{
|
{
|
||||||
@@ -72,6 +74,12 @@ namespace MTK
|
|||||||
strData = "";
|
strData = "";
|
||||||
if (_i2cCtrl.ReadData(recvBuf, wrCmds) == true)
|
if (_i2cCtrl.ReadData(recvBuf, wrCmds) == true)
|
||||||
{
|
{
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC_Read RAW] cmd=0x{data[1]:X2} recvLen={recvLen} " +
|
||||||
|
$"raw=[{string.Join(" ", recvBuf.Select(b => b.ToString("X2")))}]",
|
||||||
|
"debug");
|
||||||
|
|
||||||
|
// ────────
|
||||||
for (int i = 0; i < recvBuf.Length; ++i)
|
for (int i = 0; i < recvBuf.Length; ++i)
|
||||||
strData += recvBuf[i].ToString("X2") + " ";
|
strData += recvBuf[i].ToString("X2") + " ";
|
||||||
|
|
||||||
@@ -102,6 +110,29 @@ namespace MTK
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 从 DDC 0x6F 继续读取数据(不重新发送命令)。
|
||||||
|
/// 用于 N卡 DP AUX 通道 16 字节限制导致截断时的分段续读。
|
||||||
|
/// 在 DDC_Read 成功返回后立即调用,尝试读取响应缓冲区中尚未消费的剩余字节。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="recvLen">要读取的字节数</param>
|
||||||
|
/// <returns>读取到的字节数组,失败返回 null</returns>
|
||||||
|
public static Byte[] DDC_ReadContinue(int recvLen)
|
||||||
|
{
|
||||||
|
const Byte dest = 0x6F;
|
||||||
|
Byte[] wrCmds = { dest };
|
||||||
|
Byte[] recvBuf = new Byte[recvLen];
|
||||||
|
if (_i2cCtrl.ReadData(recvBuf, wrCmds))
|
||||||
|
{
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC_ReadContinue] rdLen={recvLen} raw=[{string.Join(" ", recvBuf.Select(b => b.ToString("X2")))}]",
|
||||||
|
"debug");
|
||||||
|
return recvBuf;
|
||||||
|
}
|
||||||
|
frmISP.WriteLog($"[DDC_ReadContinue] rdLen={recvLen} read failed", "debug");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private static Byte CalCheckSum(Byte[] data, int startIdx = 0)
|
private static Byte CalCheckSum(Byte[] data, int startIdx = 0)
|
||||||
{
|
{
|
||||||
Byte chkSum = 0;
|
Byte chkSum = 0;
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ namespace WinISP
|
|||||||
|
|
||||||
if (display) MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
if (display) MessageBox.Show(msg, "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send a 2-byte DDC command and parse the ASCII string returned by the monitor firmware.
|
/// Send a 2-byte DDC command and parse the ASCII string returned by the monitor firmware.
|
||||||
/// recv layout: [hdr0][hdr1][char0][char1]...[\0]
|
/// recv layout: [hdr0][hdr1][char0][char1]...[\0]
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -283,11 +283,15 @@ namespace WinISP
|
|||||||
{
|
{
|
||||||
Byte[] cmd = { 0xCC, subCmd };
|
Byte[] cmd = { 0xCC, subCmd };
|
||||||
int[] readLens = { 36, 32, 24, 20, 16, 12, 8 };
|
int[] readLens = { 36, 32, 24, 20, 16, 12, 8 };
|
||||||
string bestPartial = String.Empty;
|
string bestPartial = string.Empty;
|
||||||
|
bool segReadTried = false; // 分段续读只尝试一次
|
||||||
|
|
||||||
|
// N卡驱动已知的单次有效数据上限(超过此长度才信任 isComplete 判断)
|
||||||
|
// 低于或等于此值时,即使找到\0也可能是驱动填充,不能轻易认为完整
|
||||||
|
const int NV_SUSPECT_TRUNCATE_LEN = 15;
|
||||||
|
|
||||||
foreach (int readLen in readLens)
|
foreach (int readLen in readLens)
|
||||||
{
|
{
|
||||||
// Retry each readLen up to 3 times to handle NVIDIA DP AUX bus instability.
|
|
||||||
for (int attempt = 0; attempt < 3; attempt++)
|
for (int attempt = 0; attempt < 3; attempt++)
|
||||||
{
|
{
|
||||||
Byte[] recv = MTKDebugCmd.DDC_Read(cmd, readLen, delayTime);
|
Byte[] recv = MTKDebugCmd.DDC_Read(cmd, readLen, delayTime);
|
||||||
@@ -297,16 +301,22 @@ namespace WinISP
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error response pattern in logs: 6E 80 BE ...
|
// Error response: 6E 80 BE ...
|
||||||
if (recv[1] == 0x80)
|
if (recv[1] == 0x80)
|
||||||
{
|
{
|
||||||
WinIOLib.DelayMs(20);
|
WinIOLib.DelayMs(20);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int len = 0;
|
int reportedDataLen = recv[1] & 0x7F; // DDC响应头中声明的数据长度
|
||||||
while ((2 + len) < recv.Length && recv[2 + len] != 0)
|
|
||||||
len++;
|
// 先按原始方式扫描整个buffer找\0(用于完整性判断)
|
||||||
|
// 然后截断到 reportedDataLen,排除末尾DDC校验和字节
|
||||||
|
int rawLen = 0;
|
||||||
|
while ((2 + rawLen) < recv.Length && recv[2 + rawLen] != 0)
|
||||||
|
rawLen++;
|
||||||
|
|
||||||
|
int len = Math.Min(rawLen, reportedDataLen);
|
||||||
|
|
||||||
if (len <= 0)
|
if (len <= 0)
|
||||||
{
|
{
|
||||||
@@ -315,28 +325,137 @@ namespace WinISP
|
|||||||
}
|
}
|
||||||
|
|
||||||
string value = System.Text.Encoding.ASCII.GetString(recv, 2, len).Trim();
|
string value = System.Text.Encoding.ASCII.GetString(recv, 2, len).Trim();
|
||||||
|
value = TrimNonStandardDdcSuffix(value);
|
||||||
if (string.IsNullOrWhiteSpace(value))
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
{
|
{
|
||||||
WinIOLib.DelayMs(20);
|
WinIOLib.DelayMs(20);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only return when the null terminator was found inside the buffer (complete string).
|
// nullTerminatorFound: 基于原始扫描长度判断(rawLen 可能超过 reportedDataLen,
|
||||||
bool isComplete = (2 + len) < recv.Length;
|
// 因为扫过了 checksum 字节才在 padding 区遇到 \0)
|
||||||
|
bool nullTerminatorFound = (2 + rawLen) < recv.Length;
|
||||||
|
|
||||||
|
// ── 排除"驱动填充零"误判 ──────────
|
||||||
|
// 条件:\0确实存在 AND 读到的有效长度 > 驱动截断嫌疑值
|
||||||
|
// 即:len > NV_SUSPECT_TRUNCATE_LEN 才认为是真正的字符串结束符
|
||||||
|
// len <= NV_SUSPECT_TRUNCATE_LEN 时,\0 很可能是驱动填充,不可信
|
||||||
|
bool isComplete = nullTerminatorFound && len > NV_SUSPECT_TRUNCATE_LEN;
|
||||||
|
|
||||||
if (isComplete)
|
if (isComplete)
|
||||||
return value;
|
return value;
|
||||||
|
|
||||||
// Truncated (buffer full, no null found) - keep as best partial and keep retrying.
|
// ── N卡截断修复:尝试分段续读 ──────────────────────────
|
||||||
|
// 当读到的有效长度 <= NV_SUSPECT_TRUNCATE_LEN,且 DDC 响应头声明
|
||||||
|
// 还有更多数据时,尝试从 0x6F 续读剩余数据。
|
||||||
|
// N卡 DP AUX 通道单次 I2C 读取上限约 16 字节(2头 + 14数据),
|
||||||
|
// 导致长字符串(如 PanelName/BoardName)被截断。
|
||||||
|
if (!segReadTried && len <= NV_SUSPECT_TRUNCATE_LEN)
|
||||||
|
{
|
||||||
|
if (reportedDataLen > len)
|
||||||
|
{
|
||||||
|
segReadTried = true; // 标记已尝试,不再重复
|
||||||
|
int remainDataLen = reportedDataLen - len;
|
||||||
|
// 续读剩余数据 + 校验和(多读1字节余量)
|
||||||
|
Byte[] cont = MTKDebugCmd.DDC_ReadContinue(remainDataLen + 1);
|
||||||
|
|
||||||
|
if (cont != null && cont.Length >= 1)
|
||||||
|
{
|
||||||
|
// 续读数据需排除"新响应头"误判:
|
||||||
|
// 只有 [0x6E] + [Len|0x80] 同时匹配才算监视器重置了指针。
|
||||||
|
// 单独一个 0x6E(ASCII 'n')可能是字符串内容,不能误判。
|
||||||
|
bool contIsNewResponse = (cont.Length >= 2
|
||||||
|
&& cont[0] == 0x6E && (cont[1] & 0x80) != 0);
|
||||||
|
|
||||||
|
if (!contIsNewResponse)
|
||||||
|
{
|
||||||
|
// 续读数据不是新响应头 → 可能是真正的续传数据
|
||||||
|
// 只取可打印ASCII (0x20-0x7E),校验和字节(>=0x80)会被自然截断
|
||||||
|
int contLen = 0;
|
||||||
|
while (contLen < cont.Length
|
||||||
|
&& cont[contLen] >= 0x20 && cont[contLen] <= 0x7E)
|
||||||
|
contLen++;
|
||||||
|
|
||||||
|
if (contLen > 0)
|
||||||
|
{
|
||||||
|
string contStr = TrimNonStandardDdcSuffix(
|
||||||
|
Encoding.ASCII.GetString(cont, 0, contLen).Trim());
|
||||||
|
string fullValue = TrimNonStandardDdcSuffix(value + contStr);
|
||||||
|
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC] cmd=0x{subCmd:X2} 分段续读成功: '{value}' + '{contStr}' = '{fullValue}' " +
|
||||||
|
$"(first={len}, cont={contLen}, reported={reportedDataLen})",
|
||||||
|
"debug");
|
||||||
|
|
||||||
|
return fullValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 续读返回新响应头 → 监视器重置了指针,不支持续读
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC] cmd=0x{subCmd:X2} 分段续读: 监视器重置指针,续读返回新响应头 " +
|
||||||
|
$"(first={len}, reported={reportedDataLen})",
|
||||||
|
"debug");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ── End N卡截断修复 ──────────────────────────────────────
|
||||||
|
|
||||||
|
// 未通过完整性校验,记录为最优截断值,继续尝试更大 readLen
|
||||||
if (value.Length > bestPartial.Length)
|
if (value.Length > bestPartial.Length)
|
||||||
|
{
|
||||||
bestPartial = value;
|
bestPartial = value;
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC] cmd=0x{subCmd:X2} readLen={readLen} attempt={attempt} " +
|
||||||
|
$"partial='{value}' (len={len}, nullFound={nullTerminatorFound})",
|
||||||
|
"debug");
|
||||||
|
}
|
||||||
|
|
||||||
WinIOLib.DelayMs(20);
|
WinIOLib.DelayMs(20);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return longest partial result if we never got a complete one.
|
// 所有 readLen 都未能读到完整值,返回最长截断结果
|
||||||
|
// TryExpandField 负责前缀补全
|
||||||
|
if (!string.IsNullOrEmpty(bestPartial))
|
||||||
|
{
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC] cmd=0x{subCmd:X2} all readLens exhausted, best='{bestPartial}'",
|
||||||
|
"debug");
|
||||||
|
}
|
||||||
|
|
||||||
return bestPartial;
|
return bestPartial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 滤除 DDC 字符串尾部的非标准字符。
|
||||||
|
/// AMD ADL DDC 偶尔返回损坏的响应头(reportedDataLen 被放大),
|
||||||
|
/// 导致字符串尾部夹带垃圾字节(如 checksum + 下一字节)。
|
||||||
|
/// 监视器字段名合法字符集:[A-Za-z0-9 _.\\-/]
|
||||||
|
/// </summary>
|
||||||
|
private static string TrimNonStandardDdcSuffix(string raw)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(raw))
|
||||||
|
return raw;
|
||||||
|
int end = raw.Length;
|
||||||
|
while (end > 0)
|
||||||
|
{
|
||||||
|
char c = raw[end - 1];
|
||||||
|
if (char.IsLetterOrDigit(c) || c == '_' || c == ' ' || c == '.' || c == '-' || c == '/')
|
||||||
|
break;
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
if (end < raw.Length)
|
||||||
|
{
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[DDC-Sanitize] trimmed {raw.Length - end} trailing char(s): '{raw}' → '{raw.Substring(0, end)}'",
|
||||||
|
"debug");
|
||||||
|
}
|
||||||
|
return raw.Substring(0, end);
|
||||||
|
}
|
||||||
|
|
||||||
private bool TryReadMonitorIdentity(int retryCount = 5, int delayMs = 80)
|
private bool TryReadMonitorIdentity(int retryCount = 5, int delayMs = 80)
|
||||||
{
|
{
|
||||||
// Name commands are more stable in non-tool mode on some GPU/DDC paths.
|
// Name commands are more stable in non-tool mode on some GPU/DDC paths.
|
||||||
@@ -538,37 +657,77 @@ namespace WinISP
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// AMD/Intel 友好的型号读取:带响应有效性校验 + 多轮重试。
|
/// AMD/Intel 友好的型号读取:带响应有效性校验 + 多轮重试。
|
||||||
/// AMD ADL DDC 读命中率低(约 4 错 1 对),需要足够重试次数才能读到完整字符串。
|
/// AMD ADL DDC 读命中率低(约 4 错 1 对),需要足够重试次数才能读到完整字符串。
|
||||||
|
/// 采用"众数策略"而非"取最长":AMD 偶尔返回损坏的响应头会拉长字符串,
|
||||||
|
/// 统计上正确的值出现频率最高,长度众数 + 值众数投票能过滤掉偶发损坏。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private string ReadMonitorFieldRobust(byte subCmd, int expectLen, int maxRounds = 30)
|
private string ReadMonitorFieldRobust(byte subCmd, int expectLen, int maxRounds = 30)
|
||||||
{
|
{
|
||||||
string best = string.Empty;
|
var candidates = new System.Collections.Generic.List<string>();
|
||||||
int stableCount = 0; // 连续读到相同最长值的次数
|
string bestEver = string.Empty;
|
||||||
|
|
||||||
for (int round = 0; round < maxRounds; round++)
|
for (int round = 0; round < maxRounds; round++)
|
||||||
{
|
{
|
||||||
string v = DDC_GetMonitorName(subCmd, 300);
|
string v = DDC_GetMonitorName(subCmd, 300);
|
||||||
if (!string.IsNullOrWhiteSpace(v))
|
if (!string.IsNullOrWhiteSpace(v))
|
||||||
{
|
{
|
||||||
if (v.Length > best.Length)
|
// ── AMD 响应头损坏防御 ───────────────────────────────
|
||||||
|
// AMD ADL DDC 偶尔返回损坏的响应头(reportedDataLen 被放大),
|
||||||
|
// 导致字符串尾部夹带垃圾字节。虽然 DDC_GetMonitorName 已做
|
||||||
|
// 尾部字符过滤,但仍需在调用方以 expectLen 作为硬上限:
|
||||||
|
// 任何超过预期最大长度的值一律拒绝,不作为有效候选。
|
||||||
|
if (v.Length > expectLen)
|
||||||
{
|
{
|
||||||
best = v;
|
frmISP.WriteLog(
|
||||||
stableCount = 0; // 读到更长的,重置稳定计数
|
$"[AMD-Reject] cmd=0x{subCmd:X2} round#{round} value too long " +
|
||||||
frmISP.WriteLog($"[AMD-Identity] cmd=0x{subCmd:X2} round#{round} got='{v}' (len={v.Length})", "debug");
|
$"({v.Length} > expectLen={expectLen}): '{v}'",
|
||||||
}
|
"debug");
|
||||||
else if (v.Length == best.Length && v == best)
|
WinIOLib.DelayMs(50);
|
||||||
{
|
continue;
|
||||||
stableCount++; // 连续读到相同的完整值
|
|
||||||
}
|
}
|
||||||
|
// ── End AMD 响应头损坏防御 ──────────────────────────
|
||||||
|
|
||||||
// 完整性判断(二选一即退出):
|
candidates.Add(v);
|
||||||
// 1) 读到的长度已接近期望最大长度 → 认为读全
|
frmISP.WriteLog($"[AMD-Identity] cmd=0x{subCmd:X2} round#{round} got='{v}' (len={v.Length})", "debug");
|
||||||
// 2) 同一个完整值连续稳定读到 2 次 → 认为读全(应对实际长度 < expectLen 的字段)
|
|
||||||
if (best.Length >= expectLen - 1 || stableCount >= 2)
|
if (v.Length > bestEver.Length)
|
||||||
break;
|
bestEver = v;
|
||||||
|
|
||||||
|
// 提前退出优化:
|
||||||
|
// 1) 读到接近 expectLen 的值 → 大概率完整
|
||||||
|
// 2) 同一值已稳定出现 3 次 → 确信正确
|
||||||
|
if (v.Length >= expectLen - 1)
|
||||||
|
return v;
|
||||||
|
|
||||||
|
int sameCount = 0;
|
||||||
|
for (int i = candidates.Count - 1; i >= 0 && sameCount < 3; i--)
|
||||||
|
if (candidates[i] == v) sameCount++;
|
||||||
|
if (sameCount >= 3)
|
||||||
|
return v;
|
||||||
}
|
}
|
||||||
WinIOLib.DelayMs(50);
|
WinIOLib.DelayMs(50);
|
||||||
}
|
}
|
||||||
return best;
|
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
return bestEver;
|
||||||
|
|
||||||
|
// ── 众数投票:选出现最频繁的长度,再取该长度下出现最频繁的值 ──
|
||||||
|
int modeLen = candidates.GroupBy(s => s.Length)
|
||||||
|
.OrderByDescending(g => g.Count())
|
||||||
|
.First().Key;
|
||||||
|
|
||||||
|
string modeValue = candidates.Where(s => s.Length == modeLen)
|
||||||
|
.GroupBy(s => s)
|
||||||
|
.OrderByDescending(g => g.Count())
|
||||||
|
.First().Key;
|
||||||
|
|
||||||
|
if (modeValue.Length < bestEver.Length)
|
||||||
|
{
|
||||||
|
frmISP.WriteLog(
|
||||||
|
$"[AMD-Vote] cmd=0x{subCmd:X2} mode='{modeValue}' (len={modeLen}) < bestEver='{bestEver}' (len={bestEver.Length})",
|
||||||
|
"debug");
|
||||||
|
}
|
||||||
|
|
||||||
|
return modeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearConnectedMonitorInfo()
|
private void ClearConnectedMonitorInfo()
|
||||||
@@ -1567,7 +1726,7 @@ namespace WinISP
|
|||||||
// NVIDIA DP AUX can truncate DDC reads, so the fw name may be a prefix of the full name.
|
// NVIDIA DP AUX can truncate DDC reads, so the fw name may be a prefix of the full name.
|
||||||
string fwTrim = (effModel ?? string.Empty).Trim();
|
string fwTrim = (effModel ?? string.Empty).Trim();
|
||||||
string binTrim = (_u8ModelName ?? string.Empty).Trim();
|
string binTrim = (_u8ModelName ?? string.Empty).Trim();
|
||||||
if (fwTrim.Length < 6 || !binTrim.StartsWith(fwTrim, StringComparison.Ordinal))
|
if (fwTrim.Length < 6 || !binTrim.Equals(fwTrim, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
frmISP.WriteLog($"FW:{fwTrim},Bin:{binTrim}","debug");
|
frmISP.WriteLog($"FW:{fwTrim},Bin:{binTrim}","debug");
|
||||||
errMsg = "Model not match.";
|
errMsg = "Model not match.";
|
||||||
@@ -1581,7 +1740,7 @@ namespace WinISP
|
|||||||
// Prefix-match fallback: BIN chip "MST9U6" should pass against legacy effChip "MST9U".
|
// Prefix-match fallback: BIN chip "MST9U6" should pass against legacy effChip "MST9U".
|
||||||
string fwChipTrim = (effChip ?? string.Empty).Trim();
|
string fwChipTrim = (effChip ?? string.Empty).Trim();
|
||||||
string binChipTrim = (_u8ChipName ?? string.Empty).Trim();
|
string binChipTrim = (_u8ChipName ?? string.Empty).Trim();
|
||||||
if (!binChipTrim.StartsWith(fwChipTrim, StringComparison.OrdinalIgnoreCase))
|
if (!binChipTrim.Equals(fwChipTrim, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
frmISP.WriteLog($"FW:{fwChipTrim},Bin:{binChipTrim}", "debug");
|
frmISP.WriteLog($"FW:{fwChipTrim},Bin:{binChipTrim}", "debug");
|
||||||
errMsg = "Chip not match.";
|
errMsg = "Chip not match.";
|
||||||
@@ -1603,7 +1762,7 @@ namespace WinISP
|
|||||||
// Same prefix-match fallback as Model: DDC truncation may deliver only a prefix.
|
// Same prefix-match fallback as Model: DDC truncation may deliver only a prefix.
|
||||||
string fwPanelTrim = (effPanel ?? string.Empty).Trim();
|
string fwPanelTrim = (effPanel ?? string.Empty).Trim();
|
||||||
string binPanelTrim = (_u8PanelName ?? string.Empty).Trim();
|
string binPanelTrim = (_u8PanelName ?? string.Empty).Trim();
|
||||||
if (fwPanelTrim.Length < 6 || !binPanelTrim.StartsWith(fwPanelTrim, StringComparison.Ordinal))
|
if (fwPanelTrim.Length < 6 || !binPanelTrim.Equals(fwPanelTrim, StringComparison.Ordinal))
|
||||||
{
|
{
|
||||||
frmISP.WriteLog($"FW:{fwPanelTrim},Bin:{binPanelTrim}", "debug");
|
frmISP.WriteLog($"FW:{fwPanelTrim},Bin:{binPanelTrim}", "debug");
|
||||||
errMsg = "Panel not match.";
|
errMsg = "Panel not match.";
|
||||||
|
|||||||
Reference in New Issue
Block a user