const { controlButtonRegisters, motorParameterInputRegisters, readonlyParamRegisters, speedCommandRegister, statusRegisters } = require('./registers') const { parseHexInteger } = require('./base-utils') const { getSharedInputDefault, mergeInputValues, setSharedInputValues, updateDriverParams, toFiniteNumber } = require('./calculation-context') const { calculateParameterInputWriteValue, calculateSpeedCommandWriteValue, SCALE_MAX, formatFixedValue } = require('./conversions') const { updateStatusRegisterWords } = require('./status-format') const { floatToWords, getRegisterWordCache, toRegisterWord, toAddressKey, wordsToFloat } = require('./register-value-utils') const { appendInputUnit } = require('./input-value-utils') const AUTO_READ_MIN_INTERVAL = 100 const AUTO_READ_MAX_INTERVAL = 3000 const DEFAULT_AUTO_READ_INTERVAL = 100 const MOTOR_PARAM_START_ADDRESS = 0x60 const MOTOR_PARAM_WORD_COUNT = 8 const DRIVER_PARAM_START_ADDRESS = 0xA0 const STATUS_START_ADDRESS = 0xC0 const USER_STATUS_START_ADDRESS = 0xD3 const MAX_USER_STATUS_COUNT = statusRegisters.filter((item) => item.name.indexOf('用户状态字') === 0).length const DEFAULT_USER_STATUS_COUNT = 0 function getRegisterSpanWordCount(registers, startAddress) { const endAddress = registers.reduce((maxAddress, item) => { const address = parseHexInteger(item.address) if (!Number.isFinite(address)) return maxAddress return Math.max(maxAddress, address + (item.registerCount || 1)) }, startAddress) return endAddress - startAddress } const DRIVER_PARAM_WORD_COUNT = getRegisterSpanWordCount(readonlyParamRegisters, DRIVER_PARAM_START_ADDRESS) const BASE_STATUS_WORD_COUNT = USER_STATUS_START_ADDRESS - STATUS_START_ADDRESS const DRIVER_SUMMARY_REGISTER_NAMES = [ '芯片型号', '全区 Flash 校验码', '型号' ] function isDriverSummaryRegister(item) { return DRIVER_SUMMARY_REGISTER_NAMES.includes(item.name) } function getDriverReadonlyParamRegisters(registers = readonlyParamRegisters) { return registers.filter((item) => !isDriverSummaryRegister(item)) } function getInputValues(registers) { return registers.reduce((result, item) => { result[item.name] = toFiniteNumber(item.inputValue, getSharedInputDefault(item.name)) return result }, {}) } function updateMotorWriteValues(registers) { const inputValues = getInputValues(registers) return registers.map((item) => ({ ...item, writeValue: calculateParameterInputWriteValue(item, item.inputValue, inputValues) })) } function formatReadInputValue(item, value) { if (!Number.isFinite(value)) return '' if (item.name === 'LD' || item.name === 'LQ') return formatFixedValue(value, 6) if (item.name === 'RS') return formatFixedValue(value, 4) if (item.type === 'float') return formatFixedValue(value, 2) return String(Math.round(value)) } function formatHexWord(value) { return `0x${(Number(value) & 0xFFFF).toString(16).toUpperCase().padStart(4, '0')}` } function wordsToAscii(words, startIndex, byteLength) { const chars = [] const wordCount = Math.ceil(byteLength / 2) for (let index = 0; index < wordCount; index += 1) { const word = Number(words[startIndex + index]) if (!Number.isInteger(word)) break const bytes = [(word >> 8) & 0xFF, word & 0xFF] for (const byte of bytes) { if (chars.length >= byteLength || byte === 0) { return chars.join('').trim() || '--' } if (byte >= 0x20 && byte <= 0x7E) { chars.push(String.fromCharCode(byte)) } } } return chars.join('').trim() || '--' } function clampNumber(value, minValue, maxValue, fallback) { const numberValue = toFiniteNumber(value, NaN) if (!Number.isFinite(numberValue)) return fallback return Math.min(Math.max(Math.round(numberValue), minValue), maxValue) } function getUserStatusCount(value) { return clampNumber(value, 0, MAX_USER_STATUS_COUNT, DEFAULT_USER_STATUS_COUNT) } function getStatusWordCount(userStatusCount = DEFAULT_USER_STATUS_COUNT) { return BASE_STATUS_WORD_COUNT + getUserStatusCount(userStatusCount) } function cloneRegister(item) { return { ...item } } function createInitialState() { return { autoReadInterval: DEFAULT_AUTO_READ_INTERVAL, autoReadStatus: false, connectedDevice: null, controlActionButtons: controlButtonRegisters.filter((item) => item.momentary).map(cloneRegister), controlButtons: controlButtonRegisters.filter((item) => !item.momentary).map(cloneRegister), errorText: '', isAwaitingResponse: false, isReadingDriver: false, isReadingMotor: false, isSending: false, isWritingMotor: false, chipModel: '--', flashChecksum: '--', motorModel: '--', motorParameterInputRegisters, readonlyParamRegisters: getDriverReadonlyParamRegisters(), speedCommand: speedCommandRegister, systemTip: '', userStatusCount: DEFAULT_USER_STATUS_COUNT } } function applyTransportState(data, transportState) { const nextState = { connectedDevice: transportState.connectedDevice, errorText: transportState.errorText, isAwaitingResponse: transportState.isAwaitingResponse, isSending: transportState.isSending, systemTip: transportState.systemTip } if (!transportState.connectedDevice && data.autoReadStatus) { nextState.autoReadStatus = false } if (!transportState.connectedDevice) { nextState.isReadingDriver = false nextState.isReadingMotor = false nextState.isWritingMotor = false } return nextState } function applyMotorParameterInput(data, index, value) { const changedRegisters = data.motorParameterInputRegisters.map((item, currentIndex) => { if (currentIndex !== index) return item return { ...item, isDirty: true, inputValue: value } }) const nextRegisters = updateMotorWriteValues(changedRegisters) const inputValues = mergeInputValues(nextRegisters) setSharedInputValues(nextRegisters) return { motorParameterInputRegisters: nextRegisters, speedCommand: { ...data.speedCommand, isDirty: true, writeValue: calculateSpeedCommandWriteValue(data.speedCommand.inputValue, inputValues) } } } function applySpeedCommandInput(data, inputValue) { const inputValues = mergeInputValues(data.motorParameterInputRegisters) return { speedCommand: { ...data.speedCommand, isDirty: true, inputValue, writeValue: calculateSpeedCommandWriteValue(inputValue, inputValues) } } } function applySpeedCommandReadValue(data, rawValue) { const wordValue = Number(rawValue) if (!Number.isInteger(wordValue)) return {} const inputValues = mergeInputValues(data.motorParameterInputRegisters) const speedBase = toFiniteNumber(inputValues['速度基准']) const inputValue = speedBase > 0 ? appendInputUnit(data.speedCommand, formatFixedValue(wordValue / SCALE_MAX * speedBase, 2)) : data.speedCommand.inputValue return { speedCommand: { ...data.speedCommand, isDirty: false, inputValue, writeValue: String(wordValue & 0xFFFF) } } } function getControlButtonWriteValue(button) { if (!button) return 0 return button.writeValue } function getNextControlButton(button) { if (button.momentary) return button return { ...button, name: button.nextName, nextName: button.name, nextWriteValue: button.writeValue, writeValue: button.nextWriteValue } } function applyControlSuccess(data, button) { if (!button) return {} if (button.momentary) { return { systemTip: `${button.name}已下发` } } return { controlButtons: data.controlButtons.map((item) => ( item.key === button.key ? getNextControlButton(item) : item )), systemTip: `${button.name}已下发` } } function getControlButtonFromRead(button, value) { if (!button || button.momentary) return button const readValue = Number(value) if (!Number.isFinite(readValue)) return button if (Number(button.writeValue) === readValue) return getNextControlButton(button) if (Number(button.nextWriteValue) === readValue) return button return button } function applyControlReadValues(data, coilValues = {}) { return { controlButtons: data.controlButtons.map((item) => { const value = coilValues[toAddressKey(item.address)] return value === undefined ? item : getControlButtonFromRead(item, value) }) } } function buildMotorMainWriteValues(data) { const registerMap = data.motorParameterInputRegisters.reduce((result, item) => { result[item.name] = item return result }, {}) const ldWords = floatToWords(registerMap.LD && registerMap.LD.inputValue) const lqWords = floatToWords(registerMap.LQ && registerMap.LQ.inputValue) const rsWords = floatToWords(registerMap.RS && registerMap.RS.inputValue) const polePairsWord = toRegisterWord(registerMap['极对数'] && registerMap['极对数'].inputValue) const speedBaseWord = toRegisterWord(registerMap['速度基准'] && registerMap['速度基准'].inputValue) if (!ldWords || !lqWords || !rsWords || !Number.isInteger(polePairsWord) || !Number.isInteger(speedBaseWord)) { return { errorText: '请检查 LD、LQ、RS、极对数和速度基准的输入值', values: null } } return { errorText: '', values: ldWords.concat(lqWords, rsWords, [polePairsWord, speedBaseWord]) } } function applyMotorParameterReadValues(data, registerWordCache) { const nextRegisters = data.motorParameterInputRegisters.map((item) => { let readValue = null if (item.name === 'LD' && registerWordCache[0x60] !== undefined && registerWordCache[0x61] !== undefined) { readValue = wordsToFloat(registerWordCache[0x60], registerWordCache[0x61]) } else if (item.name === 'LQ' && registerWordCache[0x62] !== undefined && registerWordCache[0x63] !== undefined) { readValue = wordsToFloat(registerWordCache[0x62], registerWordCache[0x63]) } else if (item.name === 'RS' && registerWordCache[0x64] !== undefined && registerWordCache[0x65] !== undefined) { readValue = wordsToFloat(registerWordCache[0x64], registerWordCache[0x65]) } else if (item.name === '极对数' && registerWordCache[0x66] !== undefined) { readValue = registerWordCache[0x66] } else if (item.name === '速度基准' && registerWordCache[0x67] !== undefined) { readValue = registerWordCache[0x67] } if (readValue === null) return item return { ...item, isDirty: false, inputValue: appendInputUnit(item, formatReadInputValue(item, readValue)) } }) const nextWriteRegisters = updateMotorWriteValues(nextRegisters) const inputValues = mergeInputValues(nextWriteRegisters) setSharedInputValues(nextWriteRegisters) return { motorParameterInputRegisters: nextWriteRegisters, speedCommand: { ...data.speedCommand, writeValue: calculateSpeedCommandWriteValue(data.speedCommand.inputValue, inputValues) } } } function clearMotorParameterDirty(data) { return { motorParameterInputRegisters: data.motorParameterInputRegisters.map((item) => ({ ...item, isDirty: false })) } } function clearSpeedCommandDirty(data) { return { speedCommand: { ...data.speedCommand, isDirty: false } } } function applyMotorParameterBlur(data, index, value) { const item = data.motorParameterInputRegisters[index] if (!item) return {} return applyMotorParameterInput(data, index, appendInputUnit(item, value === undefined ? item.inputValue : value)) } function applySpeedCommandBlur(data, value) { return applySpeedCommandInput( data, appendInputUnit(data.speedCommand, value === undefined ? data.speedCommand.inputValue : value) ) } function applyDriverParameterReadValues(data, words) { if (!Array.isArray(words) || words.length < DRIVER_PARAM_WORD_COUNT) return {} const carrierFrequencyKHz = (words[0] >> 8) & 0xFF const baseVoltage = (words[0] & 0xFF) / 10 const opAmpGain = words[1] & 0xFFFF const samplingResistorMohm = words[2] & 0xFFFF const busVoltageDividerRatio = wordsToFloat(words[4], words[5]) const analogInputDividerRatio = wordsToFloat(words[6], words[7]) const displayValues = { 芯片型号: wordsToAscii(words, 8, 8), 型号: wordsToAscii(words, 12, 16), 载波频率: String(carrierFrequencyKHz), 基准电压: formatFixedValue(baseVoltage, 2), 运放倍数: String(opAmpGain), 采样电阻: String(samplingResistorMohm), '全区 Flash 校验码': formatHexWord(words[3]), 母线电压分压比: formatFixedValue(busVoltageDividerRatio, 2), 模拟输入电压分压比: formatFixedValue(analogInputDividerRatio, 2) } updateDriverParams({ analogInputDividerRatio, baseVoltage, busVoltageDividerRatio, carrierFrequencyKHz, opAmpGain, samplingResistorMohm }) return { chipModel: displayValues['芯片型号'], flashChecksum: displayValues['全区 Flash 校验码'], motorModel: displayValues['型号'], readonlyParamRegisters: getDriverReadonlyParamRegisters(data.readonlyParamRegisters).map((item) => ({ ...item, displayValue: displayValues[item.name] || item.displayValue || '--' })) } } function applyStatusReadValues(words, startAddress = STATUS_START_ADDRESS) { if (!Array.isArray(words) || !words.length) return {} updateStatusRegisterWords(statusRegisters, startAddress, words) return {} } module.exports = { AUTO_READ_MAX_INTERVAL, AUTO_READ_MIN_INTERVAL, DRIVER_PARAM_START_ADDRESS, DRIVER_PARAM_WORD_COUNT, MAX_USER_STATUS_COUNT, MOTOR_PARAM_START_ADDRESS, MOTOR_PARAM_WORD_COUNT, STATUS_START_ADDRESS, applyControlReadValues, applyControlSuccess, applyDriverParameterReadValues, applyMotorParameterBlur, applyMotorParameterInput, applyMotorParameterReadValues, clearMotorParameterDirty, clearSpeedCommandDirty, applySpeedCommandBlur, applySpeedCommandInput, applySpeedCommandReadValue, applyStatusReadValues, applyTransportState, buildMotorMainWriteValues, clampNumber, createInitialState, getStatusWordCount, getControlButtonWriteValue, getRegisterWordCache, getUserStatusCount, setSharedInputValues }