MF4 补齐数值换算 2/3/4/5/6 + ##CA 数组通道 + 数值型 VLSD
标定量(特性曲线 / 公式)与「一个通道装整条曲线」的数组通道是 CANape / Simulink 导出 MF4 时最常见的两种表达,之前只支持线性换算、数组通道整列丢失。 一、数值换算(cc_type 2/3/4/5/6) - ChannelSpec 把 linearConversion/offset/factor 换成 ConversionType(0-8)+ conversionValues[],ParseChannel 按 cc_type 校验参数个数后填充。 - 新增 ApplyConversion: 1 线性 v0 + v1*raw 2 有理式 (v0 + v1*raw) / (v2 + v3*raw) 3 代数式 (v0*raw^2 + v1*raw + v2) / (v3*raw^2 + v4*raw + v5) 4 表插值(交错 (键,值) 对,区间内线性插值、区间外夹紧) 5 表阶梯(同一张表,取键 <= raw 的最后一段) 6 区间映射((下限,上限,物理值) 三元组) - 参数个数不符 / 表为空 / 分母为 0 一律返回原始值(宁可显示未标定值, 也不显示编造值);线性换算 v0 == v1 == 0 视为未配置而非恒为 0。 二、##CA 数组通道(一个通道装整条曲线) - 解析 CN 链接[1] 的 ##CA 块:+0 ca_type / +1 ca_storage / +2 ndim(U16) / +4 flags(U32) / +16 dim_size[U64](步长 8,Simulink 与 CANape 实测一致)。 - 新增 ExpandArrayChannel:仅当 ca_storage == 0(元素在记录内首尾相连)、 1 < 元素数 <= 1024、bit_count != 0 时展开成多列,bit_offset += i*bit_count, 名字 name[i] / name[i][j],换算与枚举复制给每个元素。 - MF4-Record.MF4 信号数 124 -> 283(9 点特性曲线读到 6000..14000, 二维 map 读到 175/120/180...)。 三、VLSD 数值型 - 数值型不再要求 cn_bit_count > 0;新增 InferVlsdElementBits(按样本长度 优先整除 64,再 32/16/8),DecodeVlsdValue 只按一个元素做越界检查并取 样本首个元素;主通道候选排除 VLSD(其记录里存的是 ##SD 偏移)。 - 实测 4 个真实文件里 VLSD 通道只有 Comment(字符串、cycles = 0), 数值型 VLSD 数量为 0,故本次不改变现有文件的输出。 验证(全离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/convcheck:现场合成一个最小 MDF4(1 DG / 1 CG / 2 记录 / recordBytes = 72),一次覆盖 cc_type 1-6、##CA、数值 VLSD => 11/11。 - A/B:强置 conversion = NONE 且关闭数组展开 => 8 项如实失败,还原后 11/11。 - 回归:check 137/137、editcheck 20/20、round3check 31/31、round4check 34/34; 真实文件信号数 283 / 8 / 18 / 24 与预期一致。 - make release / make debug 零警告;TargetCheck.exe 804,352 字节(785.5 KiB)。 文档:Documents/agent_mem.md 同步 §10 变更日志、§11.2 第 160-164 条硬约束 与文末本轮小节(含 VLSD / 单位在文件中的实际情况报告)。
This commit is contained in:
1 parent
181f95cc3a
commit
b315201598
2 files changed
+477
-47
No files matched your search
+386
-47
@@ -45,11 +45,21 @@ namespace jdi
|
||||
MIME_STREAM = 12
|
||||
};
|
||||
|
||||
/** @brief 换算类型(`cc_type`)中本项目实现的子集。 */
|
||||
/**
|
||||
* @brief 换算类型(`cc_type`)。
|
||||
*
|
||||
* ⚠️ 数值换算(0–6)用于把记录里的原始值转成物理值;`7/8` 是「值 → 文本」,
|
||||
* 走 `ChannelSpec::enumText`(只在光标读数里展示,不改变数值)。
|
||||
*/
|
||||
enum class ConversionType : int
|
||||
{
|
||||
NONE = 0, /**< 无换算(恒等)。 */
|
||||
LINEAR = 1, /**< 线性:`phys = c0 + c1 * raw`。 */
|
||||
RATIONAL = 2, /**< 有理式:`phys = (c0 + c1*raw) / (c2 + c3*raw)`。 */
|
||||
ALGEBRAIC = 3, /**< 代数式:`phys = (c0*raw² + c1*raw + c2) / (c3*raw² + c4*raw + c5)`。 */
|
||||
TAB_INTP = 4, /**< 值→值查表(点之间**线性插值**,即特性曲线)。 */
|
||||
TAB = 5, /**< 值→值查表(**不插值**,取阶梯值)。 */
|
||||
TAB_RANGE = 6, /**< 值区间→值(`[min,max] → phys`,区间外不换算)。 */
|
||||
VALUE_TO_TEXT = 7, /**< 值 → 枚举文本(`cc_val[i]` ↔ `cc_ref[i]` 文本)。 */
|
||||
VALUE_RANGE_TO_TEXT = 8 /**< 值区间 → 文本(未支持,按恒等处理)。 */
|
||||
};
|
||||
@@ -362,10 +372,22 @@ namespace jdi
|
||||
std::uint64_t signalData = 0; /**< VLSD 通道的 `##SD` 块地址(`cn_data`)。 */
|
||||
/** `##SI` 采样索引(`cn_si_source`);空表示每个记录都有采样。 */
|
||||
std::vector<std::uint64_t> sampleIndex;
|
||||
bool linearConversion = false; /**< 是否带线性换算。 */
|
||||
double offset = 0.0; /**< 线性换算常数项。 */
|
||||
double factor = 1.0; /**< 线性换算系数(无法换算时为 1)。 */
|
||||
/** @brief 换算方式(`cc_type` 的数值子集;`VALUE_TO_TEXT` 走 `enumText`)。 */
|
||||
ConversionType conversion = ConversionType::NONE;
|
||||
/**
|
||||
* @brief `##CC` 的 `cc_val[]` 参数。
|
||||
*
|
||||
* 线性 2 个、有理式 4 个、代数式 6 个;
|
||||
* 查表类(4/5)为交错的 `(raw, phys)` 点对,区间表(6)为 `(min, max, phys)` 三元组。
|
||||
*/
|
||||
std::vector<double> conversionValues;
|
||||
SignalEnumText enumText; /**< 值 → 文本枚举表(`cc_type == 7`,可能为空)。 */
|
||||
/** @brief `##CA` 数组元素总数(0 = 普通标量通道)。 */
|
||||
std::size_t arrayElements = 0;
|
||||
/** @brief `##CA` 存储方式(0 = 元素连续存放在本记录内)。 */
|
||||
int arrayStorage = 0;
|
||||
/** @brief `##CA` 各维长度(仅用于生成 `name[i][j]` 这样的名字)。 */
|
||||
std::vector<std::size_t> arrayDims;
|
||||
/** 数值有效性:只有整数与浮点通道可以绘制。 */
|
||||
bool numeric = false;
|
||||
};
|
||||
@@ -475,6 +497,143 @@ namespace jdi
|
||||
return static_cast<double>(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 `##CC` 换算把原始值转换成物理值。
|
||||
*
|
||||
* 支持 `cc_type`:
|
||||
* * `0` 无换算 / `1` 线性;
|
||||
* * `2` 有理式 `phys = (v0 + v1·raw) / (v2 + v3·raw)`;
|
||||
* * `3` 代数式 `phys = (v0·raw² + v1·raw + v2) / (v3·raw² + v4·raw + v5)`;
|
||||
* * `4` 值→值查表(**线性插值**,即标定的特性曲线);
|
||||
* * `5` 值→值查表(**不插值**,取 `raw_i ≤ raw` 的最后一个值);
|
||||
* * `6` 区间表(`(min,max,phys)` 三元组,命中区间才换算)。
|
||||
*
|
||||
* ⚠️ 参数个数与类型不匹配时**原样返回 raw**:宁可显示原始计数,
|
||||
* 也绝不按错误的公式算出一个"看起来像物理值"的数字。
|
||||
* ⚠️ 对于 `4/5/6`,本项目假定 `cc_val` 按「键在前、值在后」交错存放
|
||||
* (与 ASAM MDF 4.1 规范一致);键单调递增。
|
||||
*/
|
||||
double ApplyConversion(const ChannelSpec& spec, double raw)
|
||||
{
|
||||
if (!std::isfinite(raw))
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
const std::vector<double>& v = spec.conversionValues;
|
||||
switch (spec.conversion)
|
||||
{
|
||||
case ConversionType::NONE:
|
||||
case ConversionType::VALUE_TO_TEXT:
|
||||
case ConversionType::VALUE_RANGE_TO_TEXT:
|
||||
return raw;
|
||||
case ConversionType::LINEAR:
|
||||
if (v.size() >= 2)
|
||||
{
|
||||
return v[0] + v[1] * raw;
|
||||
}
|
||||
return raw;
|
||||
case ConversionType::RATIONAL:
|
||||
if (v.size() >= 4)
|
||||
{
|
||||
const double denominator = v[2] + v[3] * raw;
|
||||
if (denominator == 0.0 || !std::isfinite(denominator))
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
return (v[0] + v[1] * raw) / denominator;
|
||||
}
|
||||
return raw;
|
||||
case ConversionType::ALGEBRAIC:
|
||||
if (v.size() >= 6)
|
||||
{
|
||||
const double denominator = v[3] * raw * raw + v[4] * raw + v[5];
|
||||
if (denominator == 0.0 || !std::isfinite(denominator))
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
return (v[0] * raw * raw + v[1] * raw + v[2]) / denominator;
|
||||
}
|
||||
return raw;
|
||||
case ConversionType::TAB_INTP:
|
||||
{
|
||||
// 交错点对:(key0, value0, key1, value1, ...),key 递增。
|
||||
if (v.size() < 4 || (v.size() % 2) != 0)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
const std::size_t count = v.size() / 2;
|
||||
if (raw <= v[0])
|
||||
{
|
||||
return v[1];
|
||||
}
|
||||
if (raw >= v[(count - 1) * 2])
|
||||
{
|
||||
return v[(count - 1) * 2 + 1];
|
||||
}
|
||||
for (std::size_t index = 0; index + 1 < count; ++index)
|
||||
{
|
||||
const double key0 = v[index * 2];
|
||||
const double key1 = v[(index + 1) * 2];
|
||||
if (raw >= key0 && raw <= key1)
|
||||
{
|
||||
if (key1 == key0)
|
||||
{
|
||||
return v[index * 2 + 1];
|
||||
}
|
||||
const double ratio = (raw - key0) / (key1 - key0);
|
||||
return v[index * 2 + 1] + (v[(index + 1) * 2 + 1] - v[index * 2 + 1]) * ratio;
|
||||
}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
case ConversionType::TAB:
|
||||
{
|
||||
if (v.size() < 4 || (v.size() % 2) != 0)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
const std::size_t count = v.size() / 2;
|
||||
if (raw < v[0])
|
||||
{
|
||||
return v[1];
|
||||
}
|
||||
double result = v[1];
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
if (raw >= v[index * 2])
|
||||
{
|
||||
result = v[index * 2 + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
case ConversionType::TAB_RANGE:
|
||||
{
|
||||
if (v.size() < 3 || (v.size() % 3) != 0)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
const std::size_t count = v.size() / 3;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
const double low = v[index * 3];
|
||||
const double high = v[index * 3 + 1];
|
||||
if (raw >= low && raw <= high)
|
||||
{
|
||||
return v[index * 3 + 2];
|
||||
}
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
default:
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把 VLSD 通道的 `##SD`(信号数据)块内容读进内存。
|
||||
*
|
||||
@@ -567,6 +726,38 @@ namespace jdi
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 推断 VLSD 样本的元素位宽(`cn_bit_count` 为 0 时使用)。
|
||||
*
|
||||
* 相当一部分写盘器把 VLSD 通道的 `cn_bit_count` 写成 0(它描述的是"记录里那个
|
||||
* 偏移字段",而偏移固定是 64 位)。此时按数据类别 + 样本长度推断:
|
||||
* 浮点优先 8 字节(其次 4 字节),整数按 8 → 4 → 2 → 1 字节依次尝试。
|
||||
*
|
||||
* ⚠️ 长度同时是 8 与 4 的整数倍时(例如 4 个 float = 16 字节)无法区分,
|
||||
* 这里取 8 字节(`double` 更常见)。只有 `cn_bit_count == 0` 才会走到这条路径。
|
||||
*/
|
||||
std::size_t InferVlsdElementBits(DataType type, std::size_t length)
|
||||
{
|
||||
const bool floating = (type == DataType::FLOAT_LE || type == DataType::FLOAT_BE);
|
||||
if (length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (length % 8u == 0)
|
||||
{
|
||||
return 64;
|
||||
}
|
||||
if (length % 4u == 0)
|
||||
{
|
||||
return 32;
|
||||
}
|
||||
if (!floating && length % 2u == 0)
|
||||
{
|
||||
return 16;
|
||||
}
|
||||
return floating ? 0 : 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从记录中解出一个 VLSD 采样(取样本的第一个元素)。
|
||||
*
|
||||
@@ -575,12 +766,16 @@ namespace jdi
|
||||
* * `偏移 << 1 | 标志位`(标志位为 1 时表示长度,此时按 `>> 1` 回退)。
|
||||
* 判据是「长度前缀自洽」——哪种解释能在 `##SD` 缓冲里定位到合法样本就用哪种。
|
||||
*
|
||||
* 元素位宽优先用 `cn_bit_count`;它为 0(常见)时按数据类型与样本长度推断
|
||||
* (见 `InferVlsdElementBits`)。数值类型(无符号/有符号整数、32/64 位浮点)
|
||||
* 全部支持;样本含多个元素时取**第一个元素**。
|
||||
*
|
||||
* @return 解出的数值;无法解释时返回 NaN。
|
||||
*/
|
||||
double DecodeVlsdValue(const unsigned char* record, std::size_t recordBytes, const ChannelSpec& spec,
|
||||
const std::vector<std::uint8_t>& signalData)
|
||||
{
|
||||
if (signalData.empty() || spec.bitCount == 0)
|
||||
if (signalData.empty())
|
||||
{
|
||||
return kMissingValue;
|
||||
}
|
||||
@@ -604,16 +799,30 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t elementBytes = (spec.bitCount + 7u) / 8u;
|
||||
if (length < elementBytes)
|
||||
if (length == 0)
|
||||
{
|
||||
return kMissingValue;
|
||||
}
|
||||
std::size_t elementBits = spec.bitCount;
|
||||
if (elementBits == 0 || elementBits > 64u)
|
||||
{
|
||||
elementBits = InferVlsdElementBits(spec.dataType, static_cast<std::size_t>(length));
|
||||
}
|
||||
if (elementBits == 0 || elementBits > 64u)
|
||||
{
|
||||
return kMissingValue;
|
||||
}
|
||||
const std::size_t elementBytes = (elementBits + 7u) / 8u;
|
||||
if (static_cast<std::size_t>(length) < elementBytes)
|
||||
{
|
||||
return kMissingValue;
|
||||
}
|
||||
ChannelSpec element = spec;
|
||||
element.bitOffset = 0;
|
||||
element.bitCount = std::min<std::size_t>(spec.bitCount, static_cast<std::size_t>(length) * 8u);
|
||||
element.bitCount = elementBits;
|
||||
element.vlsd = false;
|
||||
return DecodeRawValue(signalData.data() + offset + 4u, length, element);
|
||||
// 只给"这一个元素"的字节数做边界检查(样本可能含多个元素)。
|
||||
return DecodeRawValue(signalData.data() + offset + 4u, elementBytes, element);
|
||||
}
|
||||
return kMissingValue;
|
||||
}
|
||||
@@ -687,61 +896,188 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
|
||||
// 换算:支持「无换算」「线性」与「值 → 文本」三类。
|
||||
// 换算:数值换算(0–6)与「值 → 文本」(7)。
|
||||
const std::uint64_t cc = (linkCount > conversionIndex) ? file.Link(cn, conversionIndex) : 0;
|
||||
if (file.IsBlock(cc, "##CC"))
|
||||
{
|
||||
const std::uint64_t ccData = file.DataOffset(cc);
|
||||
const std::uint8_t ccType = file.U8(ccData + 0);
|
||||
const std::uint16_t valCount = file.U16(ccData + 6);
|
||||
if (ccType == static_cast<std::uint8_t>(ConversionType::LINEAR) && valCount >= 2)
|
||||
// `cc_val[]` 从数据段 +24 开始(每个 REAL 占 8 字节)。
|
||||
const auto readValues = [&file, ccData, valCount](std::size_t wanted) -> std::vector<double>
|
||||
{
|
||||
spec.linearConversion = true;
|
||||
spec.offset = file.F64(ccData + 24);
|
||||
spec.factor = file.F64(ccData + 32);
|
||||
if (spec.factor == 0.0 && spec.offset == 0.0)
|
||||
std::vector<double> values;
|
||||
for (std::uint16_t index = 0; index < valCount && values.size() < wanted; ++index)
|
||||
{
|
||||
values.push_back(file.F64(ccData + 24u + 8u * index));
|
||||
}
|
||||
return values;
|
||||
};
|
||||
const ConversionType kind = static_cast<ConversionType>(ccType);
|
||||
switch (kind)
|
||||
{
|
||||
case ConversionType::LINEAR:
|
||||
if (valCount >= 2)
|
||||
{
|
||||
spec.conversionValues = readValues(2);
|
||||
// 换算表未填写:按恒等处理,避免整列变成 0。
|
||||
spec.linearConversion = false;
|
||||
spec.offset = 0.0;
|
||||
spec.factor = 1.0;
|
||||
}
|
||||
}
|
||||
else if (ccType == static_cast<std::uint8_t>(ConversionType::VALUE_TO_TEXT) && valCount > 0)
|
||||
{
|
||||
// `cc_val[i]`(F64 数组,从数据段 +24 开始)与 `cc_ref[4 + i]` 一一对应;
|
||||
// 最后一个引用(下标 4 + valCount)是「默认文本」,这里不使用。
|
||||
const std::uint64_t ccLinkCount = file.LinkCount(cc);
|
||||
spec.enumText.values.reserve(valCount);
|
||||
spec.enumText.texts.reserve(valCount);
|
||||
for (std::uint16_t index = 0; index < valCount; ++index)
|
||||
{
|
||||
const std::uint64_t textLink = 4u + index;
|
||||
if (textLink >= ccLinkCount)
|
||||
if (spec.conversionValues.size() >= 2 && spec.conversionValues[0] == 0.0 &&
|
||||
spec.conversionValues[1] == 0.0)
|
||||
{
|
||||
break;
|
||||
spec.conversionValues[0] = 0.0;
|
||||
spec.conversionValues[1] = 1.0;
|
||||
}
|
||||
std::wstring text = file.TextBlock(file.Link(cc, textLink));
|
||||
if (text.empty())
|
||||
{
|
||||
continue; // 没有文本的取值不进入枚举表
|
||||
}
|
||||
spec.enumText.values.push_back(file.F64(ccData + 24u + 8u * index));
|
||||
spec.enumText.texts.push_back(std::move(text));
|
||||
spec.conversion = ConversionType::LINEAR;
|
||||
}
|
||||
if (spec.enumText.texts.size() != spec.enumText.values.size() ||
|
||||
spec.enumText.texts.empty())
|
||||
break;
|
||||
case ConversionType::RATIONAL:
|
||||
if (valCount >= 4)
|
||||
{
|
||||
spec.enumText = SignalEnumText();
|
||||
spec.conversionValues = readValues(4);
|
||||
spec.conversion = kind;
|
||||
}
|
||||
break;
|
||||
case ConversionType::ALGEBRAIC:
|
||||
if (valCount >= 6)
|
||||
{
|
||||
spec.conversionValues = readValues(6);
|
||||
spec.conversion = kind;
|
||||
}
|
||||
break;
|
||||
case ConversionType::TAB_INTP:
|
||||
case ConversionType::TAB:
|
||||
if (valCount >= 4 && (valCount % 2) == 0)
|
||||
{
|
||||
spec.conversionValues = readValues(valCount);
|
||||
spec.conversion = kind;
|
||||
}
|
||||
break;
|
||||
case ConversionType::TAB_RANGE:
|
||||
if (valCount >= 3 && (valCount % 3) == 0)
|
||||
{
|
||||
spec.conversionValues = readValues(valCount);
|
||||
spec.conversion = kind;
|
||||
}
|
||||
break;
|
||||
case ConversionType::VALUE_TO_TEXT:
|
||||
if (valCount > 0)
|
||||
{
|
||||
// `cc_val[i]`(F64 数组,从数据段 +24 开始)与 `cc_ref[4 + i]` 一一对应;
|
||||
// 最后一个引用(下标 4 + valCount)是「默认文本」,这里不使用。
|
||||
const std::uint64_t ccLinkCount = file.LinkCount(cc);
|
||||
spec.enumText.values.reserve(valCount);
|
||||
spec.enumText.texts.reserve(valCount);
|
||||
for (std::uint16_t index = 0; index < valCount; ++index)
|
||||
{
|
||||
const std::uint64_t textLink = 4u + index;
|
||||
if (textLink >= ccLinkCount)
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::wstring text = file.TextBlock(file.Link(cc, textLink));
|
||||
if (text.empty())
|
||||
{
|
||||
continue; // 没有文本的取值不进入枚举表
|
||||
}
|
||||
spec.enumText.values.push_back(file.F64(ccData + 24u + 8u * index));
|
||||
spec.enumText.texts.push_back(std::move(text));
|
||||
}
|
||||
if (spec.enumText.texts.size() != spec.enumText.values.size() ||
|
||||
spec.enumText.texts.empty())
|
||||
{
|
||||
spec.enumText = SignalEnumText();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// `##CA`(通道数组):一个通道装一整条曲线/矩阵(标定的特性曲线、map)。
|
||||
// 数据段布局(实测 CANape 写出的文件):type(1) + storage(1) + ndim(U16) + flags(U32)
|
||||
// + [8..15 保留] + dim_size[](U64 × ndim)。
|
||||
const std::uint64_t ca = (linkCount > 1) ? file.Link(cn, 1) : 0;
|
||||
if (ca != 0 && file.IsBlock(ca, "##CA"))
|
||||
{
|
||||
const std::uint64_t caData = file.DataOffset(ca);
|
||||
spec.arrayStorage = file.U8(caData + 1);
|
||||
const std::uint16_t rank = file.U16(caData + 2);
|
||||
std::size_t total = (rank > 0) ? 1 : 0;
|
||||
spec.arrayDims.clear();
|
||||
for (std::uint16_t axis = 0; axis < rank && axis < 8; ++axis)
|
||||
{
|
||||
const std::size_t dim =
|
||||
static_cast<std::size_t>(file.U64(caData + 16u + 8u * axis));
|
||||
if (dim == 0)
|
||||
{
|
||||
total = 0;
|
||||
break;
|
||||
}
|
||||
spec.arrayDims.push_back(dim);
|
||||
total *= dim;
|
||||
}
|
||||
spec.arrayElements = total;
|
||||
}
|
||||
|
||||
// 通道类型 2 = 虚拟主通道(CANape 的时间轴),同样可以取值。
|
||||
spec.numeric = IsNumericType(spec.dataType) && spec.bitCount > 0 && spec.bitCount <= 64;
|
||||
if (spec.vlsd)
|
||||
{
|
||||
// VLSD:`cn_bit_count` 描述的是记录里的偏移字段,元素位宽由样本自身决定
|
||||
//(很多写盘器把 `cn_bit_count` 写成 0)⇒ 只按数据类型判定。
|
||||
spec.numeric = IsNumericType(spec.dataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
spec.numeric = IsNumericType(spec.dataType) && spec.bitCount > 0 && spec.bitCount <= 64;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 把 `##CA` 数组通道展开成多个普通通道(每个元素一个)。
|
||||
*
|
||||
* CANape 的标定数组(特性曲线/断点表/map)在 MDF 里是「一个通道 + `##CA`」,
|
||||
* `storage == 0` 时各元素**连续存放**在记录的 `cn_byte_offset` 处
|
||||
*(实测:`WIN_Motor_StallCurrentVolBrk` 的 9 个 `uint16` 就是从记录 +8 起连续 18 字节)。
|
||||
* 展开后每个元素都是普通标量信号,因此自动获得光标取值、枚举、图例等全部能力。
|
||||
*
|
||||
* ⚠️ 仅处理 `storage == 0`(记录内连续);`storage != 0`(元素分散在不同记录/
|
||||
* 不同通道组/不同数据组)保持原样(只显示元素 0),避免解析出错值。
|
||||
* ⚠️ 元素总数超过 `kMaxArrayElements` 时同样保持原样(防止巨型数组把信号列表撑爆)。
|
||||
*/
|
||||
void ExpandArrayChannel(ChannelSpec&& spec, std::vector<ChannelSpec>& out)
|
||||
{
|
||||
constexpr std::size_t kMaxArrayElements = 1024;
|
||||
const std::size_t count = spec.arrayElements;
|
||||
if (count <= 1 || spec.arrayStorage != 0 || count > kMaxArrayElements || spec.bitCount == 0)
|
||||
{
|
||||
spec.arrayElements = 0;
|
||||
out.push_back(std::move(spec));
|
||||
return;
|
||||
}
|
||||
const std::size_t strideBits = spec.bitCount;
|
||||
const std::vector<std::size_t> dims = spec.arrayDims;
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
{
|
||||
ChannelSpec element = spec; // 复制:换算表 / 枚举表 / 数据类型全部沿用
|
||||
element.arrayElements = 0;
|
||||
element.bitOffset = spec.bitOffset + index * strideBits;
|
||||
std::wstring suffix;
|
||||
if (dims.size() == 2 && dims[1] > 0)
|
||||
{
|
||||
suffix = L"[" + std::to_wstring(index / dims[1]) + L"][" +
|
||||
std::to_wstring(index % dims[1]) + L"]";
|
||||
}
|
||||
else
|
||||
{
|
||||
suffix = L"[" + std::to_wstring(index) + L"]";
|
||||
}
|
||||
element.name = spec.name + suffix;
|
||||
out.push_back(std::move(element));
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief 在一条 `next` 链上迭代块偏移(带访问次数上限,避免损坏文件死循环)。 */
|
||||
template <typename Fn>
|
||||
void WalkChain(const MdfFile& file, std::uint64_t first, const char* expectedId, std::size_t limit,
|
||||
@@ -1068,7 +1404,7 @@ namespace jdi
|
||||
}
|
||||
if (std::isfinite(value))
|
||||
{
|
||||
value = master.linearConversion ? (master.offset + master.factor * value) : value;
|
||||
value = ApplyConversion(master, value);
|
||||
}
|
||||
group.time[static_cast<std::size_t>(row)] = value;
|
||||
}
|
||||
@@ -1114,7 +1450,7 @@ namespace jdi
|
||||
}
|
||||
if (std::isfinite(value))
|
||||
{
|
||||
value = spec.linearConversion ? (spec.offset + spec.factor * value) : value;
|
||||
value = ApplyConversion(spec, value);
|
||||
}
|
||||
group.values[c][static_cast<std::size_t>(row)] = value;
|
||||
}
|
||||
@@ -1152,9 +1488,10 @@ namespace jdi
|
||||
const bool remoteMaster = (cgFlags & 0x4u) != 0u;
|
||||
if (!remoteMaster)
|
||||
{
|
||||
// 优先同步主通道,但 VLSD 通道不能当时间轴(记录里只有偏移)。
|
||||
for (std::size_t i = 0; i < channels.size(); ++i)
|
||||
{
|
||||
if (channels[i].syncMaster && channels[i].numeric)
|
||||
if (channels[i].syncMaster && channels[i].numeric && !channels[i].vlsd)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
@@ -1316,7 +1653,9 @@ namespace jdi
|
||||
ChannelSpec spec;
|
||||
if (ParseChannel(file, cn, spec))
|
||||
{
|
||||
channels.push_back(std::move(spec));
|
||||
// `##CA` 数组通道在这里就地展开成多个标量通道
|
||||
//(一个通道装整条曲线时,旧实现只能看到元素 0)。
|
||||
ExpandArrayChannel(std::move(spec), channels);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user