const { atoBandwidthInputRegisters, calculatedParameterRegisters, dqGainInputRegisters, oilParameterInputRegisters, parameterInputRegisters, prepositionParameterInputRegisters, protectionRegisters, protectionSwitchRegisters, speedLoopExtraRegisters, speedSlopeRegister, tailwindSwitchRegisters, getByteRegisterValue } = require('./registers.js') const { parseHexInteger } = require('../../utils/base-utils.js') const { getSharedInputValues, mergeInputValues, toFiniteNumber } = require('./calculation-context.js') const { calculateAtoGainWriteValues, calculateDqGainWriteValue, calculateParameterInputWriteValue, calculateParameterReadValue, calculateProtectionWriteValue, calculateSpeedSlope, formatFixedValue } = require('./conversions.js') const { toAddressKey, wordsToFloat } = require('../../utils/register-value-utils.js') const { appendInputUnit } = require('./input-value-utils.js') const VSP_CURVE_ORDER = [ '开机电压', '关机电压', '调速最低电压', '调速最高电压' ] const SPEED_LOOP_INPUT_ORDER = [ '速度最小值', '速度最大值', 'SOUT_MAX' ] const TAILWIND_CALCULATED_NAMES = [ 'SPEED_KLPF_TAILWIND', 'OBS_EA_KS_TAILWIND' ] const ESTIMATOR_CALCULATED_PREFIXES = [ 'OBS_E', 'OBS_FBASE', 'OBS_EA_KS', 'SPEED_KLPF', 'FOC_KFG' ] const PROTECTION_SECTION_DEFINITIONS = [ { key: 'base', title: '基础', rows: [ [ { kind: 'switch', name: '保护使能' }, { kind: 'switch', name: '恢复使能' }, { kind: 'input', name: '故障恢复时间' } ] ] }, { key: 'hardwareCurrent', title: '硬件过流', rows: [ [ { kind: 'input', name: '硬件过流值', label: '硬件过流' } ] ] }, { key: 'current', title: '电流', rows: [ [ { kind: 'switch', name: '电流保护使能' }, { kind: 'input', name: '软件过流值' } ] ] }, { key: 'phase', title: '缺相', rows: [ [ { kind: 'switch', name: '缺相保护使能' } ] ] }, { key: 'voltage', title: '电压', rows: [ [ { kind: 'switch', name: '电压保护使能' } ], [ { kind: 'input', name: '过压保护值', label: '过压值' }, { kind: 'input', name: '欠压保护值', label: '欠压值' } ], [ { kind: 'input', name: '过压恢复值' }, { kind: 'input', name: '欠压恢复值' } ] ] }, { key: 'stall', title: '堵转', rows: [ [ { kind: 'switch', name: '堵转保护使能' } ], [ { kind: 'input', name: '速度限制最大值' }, { kind: 'input', name: '速度限制最小值' } ], [ { kind: 'input', name: '反电动势低阈值' }, { kind: 'input', name: '反电动势高阈值' } ], [ { kind: 'input', name: '速度中间值' } ] ] }, { key: 'power', title: '功率', rows: [ [ { kind: 'switch', name: '功率保护使能' }, { kind: 'input', name: '功率保护值' }, { kind: 'input', name: '功率保护时间' } ] ] }, { key: 'temperature', title: '温度', rows: [ [ { kind: 'switch', name: '温度保护使能' } ], [ { kind: 'input', name: '温度保护值' }, { kind: 'input', name: '温度恢复值' }, { kind: 'input', name: '温度保护时间' } ] ] }, { key: 'serial', title: '串口', rows: [ [ { kind: 'switch', name: '串口保护使能' }, { kind: 'input', name: '串口丢失检测时间' } ] ] }, { key: 'pwm', title: 'PWM', rows: [ [ { kind: 'switch', name: 'PWM丢失保护使能' } ] ] } ] function formatInputValue(item, value) { if (value === '' || value === undefined || value === null) return '--' const numberValue = toFiniteNumber(value, NaN) if (!Number.isFinite(numberValue)) return value if (item.type === 'uint8_t' && (numberValue < 0 || numberValue > 0xFF)) return '--' if (item.type === 'float') return formatFixedValue(numberValue, 2) return String(Math.round(numberValue)) } function getInputValues(registers) { return mergeInputValues(registers) } function getSharedParameterValues(registers, extraRegisters = []) { return { ...getSharedInputValues(), ...getInputValues(registers.concat(extraRegisters)) } } function updateAtoBandwidthValues(registers, inputValues) { return registers.map((item) => ({ ...item, ...calculateAtoGainWriteValues(item.inputValue, inputValues) })) } function updateInputWriteValues(registers) { const inputValues = getInputValues(registers) return registers.map((item) => ({ ...item, writeValue: calculateParameterInputWriteValue(item, item.inputValue, inputValues) })) } function updateSpeedLoopExtraValues(registers, inputValues) { return registers.map((item) => { const writeValue = calculateParameterInputWriteValue(item, item.inputValue, inputValues) return { ...item, actualText: '', writeValue } }) } function updateOilParameterValues(registers, inputValues) { return registers.map((item) => ({ ...item, writeValue: calculateParameterInputWriteValue(item, item.inputValue, inputValues) })) } function updateSpeedSlope(register, inputValues) { const speedSlope = calculateSpeedSlope(inputValues) return { ...register, writeValue: speedSlope === null ? '--' : formatFixedValue(speedSlope, 2) } } function addSourceIndex(registers) { return registers.map((item, index) => ({ ...item, sourceIndex: index })) } function isNameIn(names, item) { return names.includes(item.name) } function isTailwindAtoRegister(item) { return item.suffix === 'TAILWIND' } function isTailwindCalculatedRegister(item) { return isNameIn(TAILWIND_CALCULATED_NAMES, item) } function isEstimatorCalculatedRegister(item) { if (isTailwindCalculatedRegister(item)) return false return ESTIMATOR_CALCULATED_PREFIXES.some((prefix) => item.name.startsWith(prefix)) } function sortByNameOrder(registers, nameOrder) { return registers .filter((item) => nameOrder.includes(item.name)) .slice() .sort((left, right) => nameOrder.indexOf(left.name) - nameOrder.indexOf(right.name)) } function mapByName(registers) { return registers.reduce((result, item) => { result[item.name] = item return result }, {}) } function buildProtectionField(definition, registerMap, switchMap) { const source = definition.kind === 'switch' ? switchMap[definition.name] : registerMap[definition.name] if (!source) return null return { ...source, kind: definition.kind, label: definition.label || source.name, metaValue: source.writeValue === 0 ? '0' : (source.writeValue || '--') } } function buildProtectionGroups(registers, switches = []) { const protectionDisplayRegisters = addSourceIndex(registers) const protectionSwitchDisplayRegisters = addSourceIndex(switches) const registerMap = mapByName(protectionDisplayRegisters) const switchMap = mapByName(protectionSwitchDisplayRegisters) return { protectionDisplayRegisters, protectionSections: PROTECTION_SECTION_DEFINITIONS.map((section) => ({ ...section, rows: section.rows .map((row, rowIndex) => { const fields = row .map((definition) => buildProtectionField(definition, registerMap, switchMap)) .filter(Boolean) return fields.length ? { fields, key: `${section.key}-${rowIndex}` } : null }) .filter(Boolean) })).filter((section) => section.rows.length) } } function getWord(readValues, address) { return readValues.words[toAddressKey(address)] } function getRegisterReadValue(item, readValues) { if (item.area && item.area.key === 'coil') { const coilValue = readValues.coils[toAddressKey(item.address)] return coilValue === undefined ? null : coilValue } const firstWord = getWord(readValues, item.address) if (!Number.isInteger(firstWord)) return null if (item.type === 'uint8_t' && item.bytePosition) { return getByteRegisterValue(item, firstWord) } if (item.type === 'float') { const nextAddress = (parseHexInteger(item.address) + 1).toString(16).toUpperCase() return wordsToFloat(firstWord, getWord(readValues, nextAddress)) } return firstWord } function formatReadValue(item, value) { if (value === null || value === undefined || !Number.isFinite(Number(value))) return null const numberValue = Number(value) if (item.type === 'float') return formatFixedValue(numberValue, 2) return String(Math.round(numberValue)) } function getReadInputValue(item, readValue, readText, options = {}) { let inputText = readText if (options.useCalculatedInputValue) { const calculatedValue = calculateParameterReadValue(item, readValue, options.inputValues || {}) if (calculatedValue !== null) { inputText = formatFixedValue(calculatedValue, 2) } } return appendInputUnit(item, inputText) } function getDqReadInputValue(item, rawText) { const rawValue = Number(rawText) if (!Number.isFinite(rawValue)) return '' if (item.gainType === 'kp') return formatFixedValue(rawValue / 4095, 2) if (item.gainType === 'ki') return formatFixedValue(rawValue / 32767, 2) return rawText } function applyReadValuesToRegisters(registers, readValues, options = {}) { return registers.map((item) => { const readValue = getRegisterReadValue(item, readValues) const readText = formatReadValue(item, readValue) if (readText === null) return item const nextItem = { ...item, isDirty: false, writeValue: readText } if (Object.prototype.hasOwnProperty.call(item, 'value')) { nextItem.value = Number(readValue) !== 0 } if (options.updateInputValue) { nextItem.inputValue = getReadInputValue(item, readValue, readText, options) } if (options.updateDqInputValue) { nextItem.inputValue = getDqReadInputValue(item, readText) } return nextItem }) } function applyReadValuesToAtoRegisters(registers, readValues) { return registers.map((item) => { const kpWord = getWord(readValues, item.kpAddress) const kiWord = getWord(readValues, item.kiAddress) if (!Number.isInteger(kpWord) && !Number.isInteger(kiWord)) return item return { ...item, isDirty: false, kpWriteValue: Number.isInteger(kpWord) ? String(kpWord) : item.kpWriteValue, kiWriteValue: Number.isInteger(kiWord) ? String(kiWord) : item.kiWriteValue } }) } function clearDirty(registers = [], matcher = () => true) { return registers.map((item) => ( matcher(item) ? { ...item, isDirty: false } : item )) } function clearGroupDirty(data, groupKey) { const nextState = { ...data } if (groupKey === 'estimator') { nextState.atoBandwidthInputRegisters = clearDirty(data.atoBandwidthInputRegisters) nextState.calculatedParameterRegisters = clearDirty(data.calculatedParameterRegisters, isEstimatorCalculatedRegister) } if (groupKey === 'dq') nextState.dqGainInputRegisters = clearDirty(data.dqGainInputRegisters) if (groupKey === 'tailwind') { nextState.tailwindSwitchRegisters = clearDirty(data.tailwindSwitchRegisters, (item) => item.name !== '预定位启用') nextState.atoBandwidthInputRegisters = clearDirty(data.atoBandwidthInputRegisters, isTailwindAtoRegister) nextState.calculatedParameterRegisters = clearDirty(data.calculatedParameterRegisters, isTailwindCalculatedRegister) } if (groupKey === 'preposition') { nextState.tailwindSwitchRegisters = clearDirty(data.tailwindSwitchRegisters, (item) => item.name === '预定位启用') nextState.prepositionParameterInputRegisters = clearDirty(data.prepositionParameterInputRegisters) } if (groupKey === 'speedLoop') { nextState.parameterInputRegisters = clearDirty(data.parameterInputRegisters, (item) => ( SPEED_LOOP_INPUT_ORDER.includes(item.name) )) nextState.speedLoopExtraRegisters = clearDirty(data.speedLoopExtraRegisters) } if (groupKey === 'vsp') { nextState.parameterInputRegisters = clearDirty(data.parameterInputRegisters, (item) => VSP_CURVE_ORDER.includes(item.name)) nextState.speedSlopeRegister = { ...data.speedSlopeRegister, isDirty: false } } if (groupKey === 'oil') nextState.oilParameterInputRegisters = clearDirty(data.oilParameterInputRegisters) if (groupKey === 'protection') { nextState.protectionRegisters = clearDirty(data.protectionRegisters) nextState.protectionSwitchRegisters = clearDirty(data.protectionSwitchRegisters) } return { ...nextState, ...buildViewState(nextState) } } function clearRegisterDirty(registers = [], index) { return registers.map((item, currentIndex) => ( currentIndex === index ? { ...item, isDirty: false } : item )) } function clearTailwindSwitchDirty(data, index) { const nextState = { ...data, tailwindSwitchRegisters: clearRegisterDirty(data.tailwindSwitchRegisters, index) } return { ...nextState, ...buildViewState(nextState) } } function clearProtectionSwitchDirty(data, index) { const nextState = { ...data, protectionSwitchRegisters: clearRegisterDirty(data.protectionSwitchRegisters, index) } return { ...nextState, ...buildViewState(nextState) } } function buildViewState(state) { const inputRegisters = addSourceIndex(state.parameterInputRegisters) const atoRegisters = addSourceIndex(state.atoBandwidthInputRegisters) const dqRegisters = addSourceIndex(state.dqGainInputRegisters) const calculatedRegisters = addSourceIndex(state.calculatedParameterRegisters) const speedLoopExtras = addSourceIndex(state.speedLoopExtraRegisters) const tailwindSwitches = addSourceIndex(state.tailwindSwitchRegisters) return { vspCurveRegisters: sortByNameOrder(inputRegisters, VSP_CURVE_ORDER), speedLoopInputDisplayRegisters: sortByNameOrder(inputRegisters, SPEED_LOOP_INPUT_ORDER), speedLoopExtraDisplayRegisters: speedLoopExtras, atoBandwidthDisplayRegisters: atoRegisters.filter((item) => !isTailwindAtoRegister(item)), tailwindAtoBandwidthDisplayRegisters: atoRegisters.filter(isTailwindAtoRegister), tailwindCalculatedDisplayRegisters: calculatedRegisters.filter(isTailwindCalculatedRegister), tailwindControlRegisters: tailwindSwitches.filter((item) => item.name !== '预定位启用'), prepositionSwitchRegisters: tailwindSwitches.filter((item) => item.name === '预定位启用'), prepositionParameterDisplayRegisters: addSourceIndex(state.prepositionParameterInputRegisters), dqGainDisplayRegisters: dqRegisters, estimatorCalculatedDisplayRegisters: calculatedRegisters.filter(isEstimatorCalculatedRegister), ...buildProtectionGroups(state.protectionRegisters, state.protectionSwitchRegisters) } } function createInitialState() { const state = { atoBandwidthInputRegisters, atoBandwidthDisplayRegisters: [], calculatedParameterRegisters, dqGainInputRegisters, dqGainDisplayRegisters: [], estimatorCalculatedDisplayRegisters: [], oilParameterInputRegisters, parameterInputRegisters, prepositionParameterDisplayRegisters: [], prepositionParameterInputRegisters, prepositionSwitchRegisters: [], protectionDisplayRegisters: [], protectionSections: [], protectionRegisters, protectionSwitchRegisters, speedLoopExtraRegisters, speedLoopExtraDisplayRegisters: [], speedLoopInputDisplayRegisters: [], speedSlopeRegister, tailwindAtoBandwidthDisplayRegisters: [], tailwindCalculatedDisplayRegisters: [], tailwindControlRegisters: [], tailwindSwitchRegisters, vspCurveRegisters: [] } return { ...state, ...buildViewState(state) } } function refreshState(data) { const inputValues = getSharedParameterValues(data.parameterInputRegisters, data.speedLoopExtraRegisters) const nextSpeedLoopExtraRegisters = updateSpeedLoopExtraValues(data.speedLoopExtraRegisters, inputValues) const nextState = { ...data, atoBandwidthInputRegisters: updateAtoBandwidthValues(data.atoBandwidthInputRegisters, inputValues), oilParameterInputRegisters: updateOilParameterValues(data.oilParameterInputRegisters, inputValues), speedLoopExtraRegisters: nextSpeedLoopExtraRegisters, speedSlopeRegister: updateSpeedSlope(data.speedSlopeRegister, inputValues) } return { ...nextState, ...buildViewState(nextState) } } function applyReadValues(data, readValues) { const inputValues = getSharedParameterValues(data.parameterInputRegisters, data.speedLoopExtraRegisters) const nextState = { ...data, atoBandwidthInputRegisters: applyReadValuesToAtoRegisters(data.atoBandwidthInputRegisters, readValues), calculatedParameterRegisters: applyReadValuesToRegisters(data.calculatedParameterRegisters, readValues), dqGainInputRegisters: applyReadValuesToRegisters(data.dqGainInputRegisters, readValues, { updateDqInputValue: true }), oilParameterInputRegisters: applyReadValuesToRegisters(data.oilParameterInputRegisters, readValues, { inputValues, updateInputValue: true, useCalculatedInputValue: true }), parameterInputRegisters: applyReadValuesToRegisters(data.parameterInputRegisters, readValues, { inputValues, updateInputValue: true, useCalculatedInputValue: true }), prepositionParameterInputRegisters: applyReadValuesToRegisters(data.prepositionParameterInputRegisters, readValues, { updateInputValue: true }), protectionRegisters: applyReadValuesToRegisters(data.protectionRegisters, readValues, { inputValues, updateInputValue: true, useCalculatedInputValue: true }), protectionSwitchRegisters: applyReadValuesToRegisters(data.protectionSwitchRegisters, readValues), speedLoopExtraRegisters: applyReadValuesToRegisters(data.speedLoopExtraRegisters, readValues, { updateInputValue: true }), speedSlopeRegister: applyReadValuesToRegisters([data.speedSlopeRegister], readValues)[0], tailwindSwitchRegisters: applyReadValuesToRegisters(data.tailwindSwitchRegisters, readValues) } return { ...nextState, ...buildViewState(nextState) } } function applyParameterInput(data, index, value) { const changedRegisters = data.parameterInputRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value, writeValue: formatInputValue(item, value) } }) const nextRegisters = updateInputWriteValues(changedRegisters) const inputValues = getSharedParameterValues(nextRegisters, data.speedLoopExtraRegisters) const nextState = { ...data, parameterInputRegisters: nextRegisters, atoBandwidthInputRegisters: updateAtoBandwidthValues(data.atoBandwidthInputRegisters, inputValues), speedLoopExtraRegisters: updateSpeedLoopExtraValues(data.speedLoopExtraRegisters, inputValues), speedSlopeRegister: updateSpeedSlope(data.speedSlopeRegister, inputValues) } return { ...nextState, ...buildViewState(nextState) } } function applyAtoBandwidthInput(data, index, value) { const inputValues = getSharedParameterValues(data.parameterInputRegisters) const nextRegisters = data.atoBandwidthInputRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value, ...calculateAtoGainWriteValues(value, inputValues) } }) const nextState = { ...data, atoBandwidthInputRegisters: nextRegisters } return { ...nextState, ...buildViewState(nextState) } } function applyDqGainInput(data, index, value) { const nextRegisters = data.dqGainInputRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value, writeValue: calculateDqGainWriteValue(item, value) } }) const nextState = { ...data, dqGainInputRegisters: nextRegisters } return { ...nextState, ...buildViewState(nextState) } } function applySpeedLoopExtraInput(data, index, value) { const changedRegisters = data.speedLoopExtraRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value } }) const inputValues = getSharedParameterValues(data.parameterInputRegisters, changedRegisters) const nextState = { ...data, speedLoopExtraRegisters: updateSpeedLoopExtraValues(changedRegisters, inputValues) } return { ...nextState, ...buildViewState(nextState) } } function applyOilParameterInput(data, index, value) { const changedRegisters = data.oilParameterInputRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value } }) const inputValues = getSharedParameterValues(data.parameterInputRegisters, data.speedLoopExtraRegisters) const nextState = { ...data, oilParameterInputRegisters: updateOilParameterValues(changedRegisters, inputValues) } return nextState } function applyPrepositionParameterInput(data, index, value) { const nextState = { ...data, prepositionParameterInputRegisters: data.prepositionParameterInputRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value, writeValue: formatInputValue(item, value) } }) } return { ...nextState, ...buildViewState(nextState) } } function applyTailwindSwitchChange(data, index, checked) { const nextState = { ...data, tailwindSwitchRegisters: data.tailwindSwitchRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, value: checked, writeValue: checked ? 1 : 0 } }) } return { ...nextState, ...buildViewState(nextState) } } function applyProtectionSwitchChange(data, index, checked) { const nextState = { ...data, protectionSwitchRegisters: data.protectionSwitchRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, value: checked, writeValue: checked ? 1 : 0 } }) } return { ...nextState, ...buildViewState(nextState) } } function applyProtectionInput(data, index, value) { const nextRegisters = data.protectionRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value, writeValue: value === '' ? '--' : calculateProtectionWriteValue(item, toFiniteNumber(value, NaN)) } }) return { ...data, protectionRegisters: nextRegisters, ...buildProtectionGroups(nextRegisters, data.protectionSwitchRegisters) } } function getInputRegister(data, group, index) { if (group === 'parameter') return data.parameterInputRegisters[index] if (group === 'ato') return data.atoBandwidthInputRegisters[index] if (group === 'dq') return data.dqGainInputRegisters[index] if (group === 'speedLoopExtra') return data.speedLoopExtraRegisters[index] if (group === 'oil') return data.oilParameterInputRegisters[index] if (group === 'preposition') return data.prepositionParameterInputRegisters[index] if (group === 'protection') return data.protectionRegisters[index] return null } function applyInputBlur(data, group, index, value) { const item = getInputRegister(data, group, index) if (!item) return data const inputValue = appendInputUnit(item, value === undefined ? item.inputValue : value) if (group === 'parameter') return applyParameterInput(data, index, inputValue) if (group === 'ato') return applyAtoBandwidthInput(data, index, inputValue) if (group === 'dq') return applyDqGainInput(data, index, inputValue) if (group === 'speedLoopExtra') return applySpeedLoopExtraInput(data, index, inputValue) if (group === 'oil') return applyOilParameterInput(data, index, inputValue) if (group === 'preposition') return applyPrepositionParameterInput(data, index, inputValue) if (group === 'protection') return applyProtectionInput(data, index, inputValue) return data } module.exports = { applyAtoBandwidthInput, clearGroupDirty, clearProtectionSwitchDirty, clearTailwindSwitchDirty, applyDqGainInput, applyInputBlur, applyOilParameterInput, applyParameterInput, applyPrepositionParameterInput, applyProtectionInput, applyProtectionSwitchChange, applyReadValues, applySpeedLoopExtraInput, applyTailwindSwitchChange, createInitialState, refreshState }