115 lines
3.2 KiB
TypeScript
115 lines
3.2 KiB
TypeScript
export type WeightUnit = "kg" | "g" | "lb";
|
|
|
|
const UNIT_FACTORS: Record<WeightUnit, number> = {
|
|
kg: 1,
|
|
g: 1000,
|
|
lb: 2.20462262,
|
|
};
|
|
|
|
/**
|
|
* RS-485 Modbus RTU 默认配置。
|
|
* 留空或传 "{}" 时使用此配置。
|
|
* 针对 Firefly AIO-3399C 工业终端的 /dev/ttysWK0 (RS485 端口)。
|
|
*/
|
|
export const DEFAULT_MODBUS_CONFIG = {
|
|
portPath: "/dev/ttysWK0",
|
|
baudRate: 9600,
|
|
slaveId: 1,
|
|
functionCode: 4,
|
|
registerAddress: 0,
|
|
registerCount: 2,
|
|
dataType: "float32",
|
|
byteOrder: "ABCD",
|
|
pollIntervalMs: 125,
|
|
};
|
|
|
|
/**
|
|
* 将内部统一保存的千克值转换为界面单位,并固定显示两位小数。
|
|
*/
|
|
export function formatWeight(valueKg: number, unit: WeightUnit) {
|
|
const safeValue = Number.isFinite(valueKg) ? Math.max(0, valueKg) : 0;
|
|
return (safeValue * UNIT_FACTORS[unit]).toFixed(2);
|
|
}
|
|
|
|
/**
|
|
* 把 ISO 时间转换为终端文件名使用的本地时间格式:YYYYMMDDHHmmss。
|
|
*/
|
|
export function createFileTimestamp(iso: string) {
|
|
const date = new Date(iso);
|
|
const pad = (value: number) => value < 10 ? `0${value}` : String(value);
|
|
|
|
return [
|
|
date.getFullYear(),
|
|
pad(date.getMonth() + 1),
|
|
pad(date.getDate()),
|
|
pad(date.getHours()),
|
|
pad(date.getMinutes()),
|
|
pad(date.getSeconds()),
|
|
].join("");
|
|
}
|
|
|
|
/**
|
|
* 兼容电子秤服务常见的消息格式:
|
|
* - 纯数字,例如 `2.49`
|
|
* - JSON,例如 `{"weight": 2490, "unit": "g"}`
|
|
* - JSON 千克字段,例如 `{"weightKg": 2.49}`
|
|
*
|
|
* 返回值统一为千克;无效值、负数或不支持的单位返回 null。
|
|
*/
|
|
export function parseScaleMessage(message: string): number | null {
|
|
const trimmed = message.trim();
|
|
if (!trimmed) return null;
|
|
|
|
let payload: unknown = trimmed;
|
|
try {
|
|
payload = JSON.parse(trimmed);
|
|
} catch {
|
|
// 非 JSON 消息继续按纯数字处理。
|
|
}
|
|
|
|
if (typeof payload === "number" || typeof payload === "string") {
|
|
const value = Number(payload);
|
|
return Number.isFinite(value) && value >= 0 ? value : null;
|
|
}
|
|
|
|
if (!payload || typeof payload !== "object") return null;
|
|
|
|
const record = payload as Record<string, unknown>;
|
|
const rawValue = record.weightKg ?? record.weight ?? record.value;
|
|
const value = Number(rawValue);
|
|
if (!Number.isFinite(value) || value < 0) return null;
|
|
|
|
const unit = String(record.unit ?? (record.weightKg != null ? "kg" : "kg")).toLowerCase();
|
|
if (unit === "kg") return value;
|
|
if (unit === "g") return value / 1000;
|
|
if (unit === "lb" || unit === "lbs") return value / UNIT_FACTORS.lb;
|
|
return null;
|
|
}
|
|
|
|
export type StabilityResult = {
|
|
stable: boolean;
|
|
progress: number;
|
|
};
|
|
|
|
/**
|
|
* 根据连续采样窗口判断重量是否稳定。
|
|
* 至少需要 6 个有效样本,样本极差小于 8 克时判定稳定。
|
|
*/
|
|
export function evaluateStability(samples: number[]): StabilityResult {
|
|
// 不使用 Array.prototype.at,确保兼容终端内置的 Chrome 52 WebView。
|
|
const latest = samples.length ? samples[samples.length - 1] : 0;
|
|
if (samples.length < 6 || latest < 0.01) {
|
|
return {
|
|
stable: false,
|
|
progress: Math.min(85, samples.length * 14),
|
|
};
|
|
}
|
|
|
|
const spread = Math.max(...samples) - Math.min(...samples);
|
|
const stable = spread < 0.008;
|
|
return {
|
|
stable,
|
|
progress: stable ? 100 : Math.max(25, 90 - spread * 500),
|
|
};
|
|
}
|