Browse Source

feat: 上位机数据修正功能

master
liujiangyong 2 weeks ago
parent
commit
fadb36b284
  1. 64
      CHANGELOGS/V1.2.2.md
  2. 2
      package.json
  3. 29
      src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx
  4. 141
      src/renderer/src/components/SystemSettings/SystemSettings.jsx
  5. 4
      src/renderer/src/components/SystemSettings/SystemSettings.module.css
  6. 17
      src/renderer/src/stores/deviceStore.js
  7. 60
      src/renderer/src/utils/deflectionCorrection.js

64
CHANGELOGS/V1.2.2.md

@ -0,0 +1,64 @@
# V1.2.2 更新日志
- **更新日期**: 2026年06月09日
- **版本号**: 1.2.2
## 新增功能
### 1. 数据修正系数配置
- **新增数据修正系数功能**:在系统设置中新增 X/Y 双方向修正系数配置,支持分别设置 `xK`、`xB`、`yK`、`yB`
- **支持线性修正公式**:页面显示值按 `newValue = K * originalValue + B` 计算
- **本地持久化保存**:修正系数自动保存到本地,上位机重启后可继续沿用
- **页面更新提示**:修正系数修改成功后,页面会给出即时提示,方便确认操作结果
### 2. 修正值统一应用
- **曲线显示统一使用修正值**:X/Y 方向曲线改为显示修正后的数据
- **实时数据显示统一使用修正值**:右侧实时数据显示与曲线口径保持一致
- **报警判断统一使用修正值**:报警逻辑基于修正后的 X/Y 数据进行判断
- **CSV 导出统一使用修正值**:实时数据与报警数据导出均使用修正后的值
## 技术细节
### 修正系数
- 修正系数项:
- `xK`
- `xB`
- `yK`
- `yB`
- 默认值:
- `xK = 1`
- `xB = 0`
- `yK = 1`
- `yB = 0`
### 数据处理策略
- 页面接收到实时传感器数据后,先计算并冻结:
- `correctedX`
- `correctedY`
- 冻结结果用于:
- 历史曲线显示
- 实时数据显示
- 报警判断
- 实时 CSV 导出
- 报警 CSV 导出
## 影响范围
- 挠度采集页 X/Y 曲线显示
- 挠度采集页实时数据显示
- 系统设置中的数据修正系数配置
- 报警判断逻辑
- 实时数据 CSV 导出
- 报警数据 CSV 导出
## 依赖更新
- 无依赖包更新
## 注意事项
1. 修正系数仅作用于当前上位机本地显示与导出,不会下发到设备端。
2. 设备侧“测点设置”中的“计算系数”仍为独立参数,与本次新增的上位机修正系数互不替代。
3. 修改修正系数后,仅后续新采集数据使用新系数,已有历史数据保持采集当时的结果。
4. 如需验证导出结果,建议同时比对页面曲线、实时数据显示与 CSV 记录的一致性。
---
**完整更新内容请查看项目 Git 提交记录**

2
package.json

@ -1,6 +1,6 @@
{ {
"name": "FlexometerSetup", "name": "FlexometerSetup",
"version": "1.2.1", "version": "1.2.2",
"description": "An Electron application with React", "description": "An Electron application with React",
"main": "./out/main/index.js", "main": "./out/main/index.js",
"author": "cles", "author": "cles",

29
src/renderer/src/components/DeflectionCollection/DeflectionCollection.jsx

@ -1,6 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { IPC_EVENT } from '../../common/ipcEvents' import { IPC_EVENT } from '../../common/ipcEvents'
import useDeviceStore from '../../stores/deviceStore' import useDeviceStore from '../../stores/deviceStore'
import { applyCorrectionToSensors } from '../../utils/deflectionCorrection'
import { Line } from 'react-chartjs-2' import { Line } from 'react-chartjs-2'
import { import {
Chart as ChartJS, Chart as ChartJS,
@ -24,6 +25,12 @@ function DeflectionCollection() {
const connectedDevice = useDeviceStore((state) => state.connectedDevice) const connectedDevice = useDeviceStore((state) => state.connectedDevice)
const alarmEnabled = useDeviceStore((state) => state.alarmEnabled) const alarmEnabled = useDeviceStore((state) => state.alarmEnabled)
const alarmLimits = useDeviceStore((state) => state.alarmLimits) const alarmLimits = useDeviceStore((state) => state.alarmLimits)
const correctionFactors = useDeviceStore((state) => state.correctionFactors)
const correctionFactorsRef = useRef(correctionFactors)
useEffect(() => {
correctionFactorsRef.current = correctionFactors
}, [correctionFactors])
// //
const targetColors = [ const targetColors = [
@ -54,7 +61,7 @@ function DeflectionCollection() {
if (data && data.values && data.values.sensors && Array.isArray(data.values.sensors)) { if (data && data.values && data.values.sensors && Array.isArray(data.values.sensors)) {
const newDataPoint = { const newDataPoint = {
timestamp: data.values.timestamp || new Date().toISOString(), timestamp: data.values.timestamp || new Date().toISOString(),
sensors: data.values.sensors sensors: applyCorrectionToSensors(data.values.sensors, correctionFactorsRef.current)
} }
setSensorDataHistory((prev) => { setSensorDataHistory((prev) => {
@ -104,7 +111,7 @@ function DeflectionCollection() {
label: `测点${sensor.pos}`, label: `测点${sensor.pos}`,
data: sensorDataHistory.map((dataPoint) => { data: sensorDataHistory.map((dataPoint) => {
const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos) const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos)
return sensorData ? Number(sensorData.yReal) : null return sensorData ? sensorData.correctedY : null
}), }),
borderColor: sensor.color, borderColor: sensor.color,
backgroundColor: sensor.color.replace('1)', '0.2)'), backgroundColor: sensor.color.replace('1)', '0.2)'),
@ -150,7 +157,7 @@ function DeflectionCollection() {
label: `测点${sensor.pos}`, label: `测点${sensor.pos}`,
data: sensorDataHistory.map((dataPoint) => { data: sensorDataHistory.map((dataPoint) => {
const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos) const sensorData = dataPoint.sensors.find((s) => s.pos === sensor.pos)
return sensorData ? Number(sensorData.xReal) : null return sensorData ? sensorData.correctedX : null
}), }),
borderColor: sensor.color, borderColor: sensor.color,
backgroundColor: sensor.color.replace('1)', '0.2)'), backgroundColor: sensor.color.replace('1)', '0.2)'),
@ -219,7 +226,9 @@ function DeflectionCollection() {
// //
const latestData = const latestData =
sensorDataHistory.length > 0 ? sensorDataHistory[sensorDataHistory.length - 1] : null sensorDataHistory.length > 0
? sensorDataHistory[sensorDataHistory.length - 1]
: null
return ( return (
<div <div
@ -404,7 +413,10 @@ function DeflectionCollection() {
marginLeft: '5px' marginLeft: '5px'
}} }}
> >
X {Number(sensor.xReal).toFixed(3)} X {' '}
{sensor.correctedX !== null && sensor.correctedX !== undefined
? Number(sensor.correctedX).toFixed(3)
: '无数据'}
</span> </span>
</div> </div>
@ -422,7 +434,10 @@ function DeflectionCollection() {
marginLeft: '5px' marginLeft: '5px'
}} }}
> >
Y {Number(sensor.yReal).toFixed(3)} Y {' '}
{sensor.correctedY !== null && sensor.correctedY !== undefined
? Number(sensor.correctedY).toFixed(3)
: '无数据'}
</span> </span>
</div> </div>
</div> </div>

141
src/renderer/src/components/SystemSettings/SystemSettings.jsx

@ -12,6 +12,12 @@ import { useState, useEffect, useCallback, useRef } from 'react'
import { IPC_EVENT } from '../../common/ipcEvents' import { IPC_EVENT } from '../../common/ipcEvents'
import useDeviceStore from '../../stores/deviceStore' import useDeviceStore from '../../stores/deviceStore'
import AlgorithmSettings from '../AlgorithmSettings/AlgorithmSettings' import AlgorithmSettings from '../AlgorithmSettings/AlgorithmSettings'
import {
applyCorrectionToSensor,
DEFAULT_CORRECTION_FACTORS,
DEFLECTION_CORRECTION_STORAGE_KEY,
sanitizeCorrectionFactors
} from '../../utils/deflectionCorrection'
function SystemSettings() { function SystemSettings() {
// //
@ -37,11 +43,13 @@ function SystemSettings() {
const isReconnecting = useDeviceStore((state) => state.isReconnecting) const isReconnecting = useDeviceStore((state) => state.isReconnecting)
const alarmEnabled = useDeviceStore((state) => state.alarmEnabled) const alarmEnabled = useDeviceStore((state) => state.alarmEnabled)
const alarmLimits = useDeviceStore((state) => state.alarmLimits) const alarmLimits = useDeviceStore((state) => state.alarmLimits)
const correctionFactors = useDeviceStore((state) => state.correctionFactors)
const setReconnectEnabled = useDeviceStore((state) => state.setReconnectEnabled) const setReconnectEnabled = useDeviceStore((state) => state.setReconnectEnabled)
const setReconnectInterval = useDeviceStore((state) => state.setReconnectInterval) const setReconnectInterval = useDeviceStore((state) => state.setReconnectInterval)
const setReconnecting = useDeviceStore((state) => state.setReconnecting) const setReconnecting = useDeviceStore((state) => state.setReconnecting)
const setAlarmEnabled = useDeviceStore((state) => state.setAlarmEnabled) const setAlarmEnabled = useDeviceStore((state) => state.setAlarmEnabled)
const setAlarmLimits = useDeviceStore((state) => state.setAlarmLimits) const setAlarmLimits = useDeviceStore((state) => state.setAlarmLimits)
const setCorrectionFactors = useDeviceStore((state) => state.setCorrectionFactors)
// 使ref // 使ref
const realtimeDataEnabledRef = useRef(realtimeDataEnabled) const realtimeDataEnabledRef = useRef(realtimeDataEnabled)
@ -50,6 +58,7 @@ function SystemSettings() {
const alarmDataEnabledRef = useRef(alarmDataEnabled) const alarmDataEnabledRef = useRef(alarmDataEnabled)
const alarmEnabledRef = useRef(alarmEnabled) const alarmEnabledRef = useRef(alarmEnabled)
const alarmLimitsRef = useRef(alarmLimits) const alarmLimitsRef = useRef(alarmLimits)
const correctionFactorsRef = useRef(correctionFactors)
// ref // ref
useEffect(() => { useEffect(() => {
@ -76,6 +85,10 @@ function SystemSettings() {
alarmLimitsRef.current = alarmLimits alarmLimitsRef.current = alarmLimits
}, [alarmLimits]) }, [alarmLimits])
useEffect(() => {
correctionFactorsRef.current = correctionFactors
}, [correctionFactors])
// //
useEffect(() => { useEffect(() => {
const reconnectStatusHandler = (event, result) => { const reconnectStatusHandler = (event, result) => {
@ -119,6 +132,23 @@ function SystemSettings() {
} }
}, []) }, [])
useEffect(() => {
const savedFactors = localStorage.getItem(DEFLECTION_CORRECTION_STORAGE_KEY)
if (!savedFactors) {
setCorrectionFactors(DEFAULT_CORRECTION_FACTORS)
return
}
try {
const parsedFactors = JSON.parse(savedFactors)
setCorrectionFactors(sanitizeCorrectionFactors(parsedFactors))
} catch (error) {
console.error('读取数据修正系数失败:', error)
setCorrectionFactors(DEFAULT_CORRECTION_FACTORS)
}
}, [setCorrectionFactors])
// CSV // CSV
const writeRealtimeDataToCSV = useCallback( const writeRealtimeDataToCSV = useCallback(
async (data) => { async (data) => {
@ -255,8 +285,19 @@ function SystemSettings() {
// //
if (data && data.values && data.values.sensors && Array.isArray(data.values.sensors)) { if (data && data.values && data.values.sensors && Array.isArray(data.values.sensors)) {
const frozenSensors = data.values.sensors.map((sensor) =>
applyCorrectionToSensor(sensor, correctionFactorsRef.current)
)
const frozenData = {
...data,
values: {
...data.values,
sensors: frozenSensors
}
}
// //
writeRealtimeDataToCSV(data) writeRealtimeDataToCSV(frozenData)
// //
// 使ref // 使ref
@ -266,10 +307,10 @@ function SystemSettings() {
connectedDeviceRef.current?.ip && connectedDeviceRef.current?.ip &&
storagePathRef.current storagePathRef.current
) { ) {
const alarmSensors = checkAlarmConditions(data.values.sensors, alarmLimitsRef.current) const alarmSensors = checkAlarmConditions(frozenSensors, alarmLimitsRef.current)
if (alarmSensors.length > 0) { if (alarmSensors.length > 0) {
// console.log(':', alarmSensors) // console.log(':', alarmSensors)
writeAlarmDataToCSV(data, alarmSensors) writeAlarmDataToCSV(frozenData, alarmSensors)
} }
} }
} }
@ -327,8 +368,8 @@ function SystemSettings() {
headers.push( headers.push(
`测点 ${i} 基准标靶`, `测点 ${i} 基准标靶`,
`测点 ${i} 计算系数`, `测点 ${i} 计算系数`,
`测点 ${i}xReal 坐标`, `测点 ${i} X修正值`,
`测点 ${i}yReal 坐标` `测点 ${i} Y修正值`
) )
} }
return headers.join(',') + '\n' return headers.join(',') + '\n'
@ -341,8 +382,8 @@ function SystemSettings() {
'测点位置', '测点位置',
'基准标靶', '基准标靶',
'计算系数', '计算系数',
'xReal坐标', 'X修正值',
'yReal坐标', 'Y修正值',
'报警类型', '报警类型',
'阈值范围', '阈值范围',
'超出值' '超出值'
@ -364,8 +405,12 @@ function SystemSettings() {
row.push( row.push(
sensor.tar || '', sensor.tar || '',
sensor.arg || '', sensor.arg || '',
sensor.xReal !== null && sensor.xReal !== undefined ? sensor.xReal : '无数据', sensor.correctedX !== null && sensor.correctedX !== undefined
sensor.yReal !== null && sensor.yReal !== undefined ? sensor.yReal : '无数据' ? sensor.correctedX
: '无数据',
sensor.correctedY !== null && sensor.correctedY !== undefined
? sensor.correctedY
: '无数据'
) )
} else { } else {
// //
@ -386,8 +431,8 @@ function SystemSettings() {
sensor.pos || '', sensor.pos || '',
sensor.tar || '', sensor.tar || '',
sensor.arg || '', sensor.arg || '',
sensor.xReal !== null && sensor.xReal !== undefined ? sensor.xReal : '无数据', sensor.correctedX !== null && sensor.correctedX !== undefined ? sensor.correctedX : '无数据',
sensor.yReal !== null && sensor.yReal !== undefined ? sensor.yReal : '无数据', sensor.correctedY !== null && sensor.correctedY !== undefined ? sensor.correctedY : '无数据',
alarmType, alarmType,
threshold, threshold,
exceedValue exceedValue
@ -407,42 +452,42 @@ function SystemSettings() {
sensors.forEach((sensor) => { sensors.forEach((sensor) => {
if (!sensor) return if (!sensor) return
const { xReal, yReal } = sensor const { correctedX, correctedY } = sensor
// X // X
if (xReal !== null && xReal !== undefined) { if (correctedX !== null && correctedX !== undefined) {
if (xReal > limits.xUpper) { if (correctedX > limits.xUpper) {
alarmSensors.push({ alarmSensors.push({
sensor, sensor,
alarmType: 'X轴上限超出', alarmType: 'X轴上限超出',
threshold: `±${limits.xUpper}`, threshold: `±${limits.xUpper}`,
exceedValue: (xReal - limits.xUpper).toFixed(2) exceedValue: (correctedX - limits.xUpper).toFixed(2)
}) })
} else if (xReal < limits.xLower) { } else if (correctedX < limits.xLower) {
alarmSensors.push({ alarmSensors.push({
sensor, sensor,
alarmType: 'X轴下限超出', alarmType: 'X轴下限超出',
threshold: `±${Math.abs(limits.xLower)}`, threshold: `±${Math.abs(limits.xLower)}`,
exceedValue: (limits.xLower - xReal).toFixed(2) exceedValue: (limits.xLower - correctedX).toFixed(2)
}) })
} }
} }
// Y // Y
if (yReal !== null && yReal !== undefined) { if (correctedY !== null && correctedY !== undefined) {
if (yReal > limits.yUpper) { if (correctedY > limits.yUpper) {
alarmSensors.push({ alarmSensors.push({
sensor, sensor,
alarmType: 'Y轴上限超出', alarmType: 'Y轴上限超出',
threshold: `±${limits.yUpper}`, threshold: `±${limits.yUpper}`,
exceedValue: (yReal - limits.yUpper).toFixed(2) exceedValue: (correctedY - limits.yUpper).toFixed(2)
}) })
} else if (yReal < limits.yLower) { } else if (correctedY < limits.yLower) {
alarmSensors.push({ alarmSensors.push({
sensor, sensor,
alarmType: 'Y轴下限超出', alarmType: 'Y轴下限超出',
threshold: `±${Math.abs(limits.yLower)}`, threshold: `±${Math.abs(limits.yLower)}`,
exceedValue: (limits.yLower - yReal).toFixed(2) exceedValue: (limits.yLower - correctedY).toFixed(2)
}) })
} }
} }
@ -632,6 +677,21 @@ function SystemSettings() {
} }
} }
const handleCorrectionFactorChange = (field, value) => {
if (value === null || value === undefined || !Number.isFinite(Number(value))) {
return
}
const nextFactors = {
...correctionFactors,
[field]: Number(value)
}
setCorrectionFactors(nextFactors)
localStorage.setItem(DEFLECTION_CORRECTION_STORAGE_KEY, JSON.stringify(nextFactors))
message.success(`修正系数 ${field} 已更新`)
}
return ( return (
<Flex vertical gap={4} className={styles.container}> <Flex vertical gap={4} className={styles.container}>
<div className={styles.header}> <div className={styles.header}>
@ -820,6 +880,43 @@ function SystemSettings() {
/> />
</Flex> </Flex>
</div> </div>
<div className={styles.subSection}>
<div className={styles.subSectionTitle}>数据修正系数</div>
<Flex gap="middle" className={styles.correctionInputs} vertical>
<InputNumber
precision={6}
addonBefore={'X轴 K'}
className={styles.inputNumber}
value={correctionFactors.xK}
onChange={(value) => handleCorrectionFactorChange('xK', value)}
/>
<InputNumber
precision={6}
addonBefore={'X轴 B'}
className={styles.inputNumber}
value={correctionFactors.xB}
onChange={(value) => handleCorrectionFactorChange('xB', value)}
/>
<InputNumber
precision={6}
addonBefore={'Y轴 K'}
className={styles.inputNumber}
value={correctionFactors.yK}
onChange={(value) => handleCorrectionFactorChange('yK', value)}
/>
<InputNumber
precision={6}
addonBefore={'Y轴 B'}
className={styles.inputNumberLast}
value={correctionFactors.yB}
onChange={(value) => handleCorrectionFactorChange('yB', value)}
/>
</Flex>
</div>
</div> </div>
{/* 存储设置 */} {/* 存储设置 */}

4
src/renderer/src/components/SystemSettings/SystemSettings.module.css

@ -116,6 +116,10 @@
margin-bottom: 6px; margin-bottom: 6px;
} }
.correctionInputs {
margin-bottom: 6px;
}
.storageSection { .storageSection {
border: 1px solid #eee; border: 1px solid #eee;
border-radius: 6px; border-radius: 6px;

17
src/renderer/src/stores/deviceStore.js

@ -26,6 +26,14 @@ const useDeviceStore = create((set) => ({
yLower: -15 yLower: -15
}, },
// 数据修正系数
correctionFactors: {
xK: 1,
xB: 0,
yK: 1,
yB: 0
},
// 设置连接的设备 // 设置连接的设备
setConnectedDevice: (device) => set({ connectedDevice: device }), setConnectedDevice: (device) => set({ connectedDevice: device }),
@ -49,6 +57,15 @@ const useDeviceStore = create((set) => ({
setAlarmEnabled: (enabled) => set({ alarmEnabled: enabled }), setAlarmEnabled: (enabled) => set({ alarmEnabled: enabled }),
setAlarmLimits: (limits) => set({ alarmLimits: limits }), setAlarmLimits: (limits) => set({ alarmLimits: limits }),
// 数据修正系数管理
setCorrectionFactors: (factors) =>
set((state) => ({
correctionFactors: {
...state.correctionFactors,
...factors
}
})),
// 获取当前连接的IP // 获取当前连接的IP
getConnectedIp: () => { getConnectedIp: () => {
const state = useDeviceStore.getState() const state = useDeviceStore.getState()

60
src/renderer/src/utils/deflectionCorrection.js

@ -0,0 +1,60 @@
export const DEFLECTION_CORRECTION_STORAGE_KEY = 'deflectionCorrectionFactors'
export const DEFAULT_CORRECTION_FACTORS = {
xK: 1,
xB: 0,
yK: 1,
yB: 0
}
const toFiniteNumber = (value) => {
const num = Number(value)
return Number.isFinite(num) ? num : null
}
export const sanitizeCorrectionFactors = (value) => {
const next = value && typeof value === 'object' ? value : {}
return {
xK: toFiniteNumber(next.xK) ?? DEFAULT_CORRECTION_FACTORS.xK,
xB: toFiniteNumber(next.xB) ?? DEFAULT_CORRECTION_FACTORS.xB,
yK: toFiniteNumber(next.yK) ?? DEFAULT_CORRECTION_FACTORS.yK,
yB: toFiniteNumber(next.yB) ?? DEFAULT_CORRECTION_FACTORS.yB
}
}
export const correctAxisValue = (originalValue, k, b) => {
if (originalValue === null || originalValue === undefined || originalValue === '') {
return null
}
const numericOriginal = Number(originalValue)
if (!Number.isFinite(numericOriginal)) {
return null
}
return k * numericOriginal + b
}
export const applyCorrectionToSensor = (sensor, correctionFactors) => {
if (!sensor || typeof sensor !== 'object') {
return sensor
}
const normalizedFactors = sanitizeCorrectionFactors(correctionFactors)
return {
...sensor,
appliedCorrectionFactors: normalizedFactors,
correctedX: correctAxisValue(sensor.xReal, normalizedFactors.xK, normalizedFactors.xB),
correctedY: correctAxisValue(sensor.yReal, normalizedFactors.yK, normalizedFactors.yB)
}
}
export const applyCorrectionToSensors = (sensors, correctionFactors) => {
if (!Array.isArray(sensors)) {
return []
}
return sensors.map((sensor) => applyCorrectionToSensor(sensor, correctionFactors))
}
Loading…
Cancel
Save