const { buildReadFrame, buildWriteMultipleRegistersFrame, buildWriteSingleCoilFrame, buildWriteSingleRegisterFrame, MAX_READ_REGISTER_QUANTITY } = require('./modbus-rtu') const controlState = require('./control-page-state') const transport = require('./ble-transport') const { addCoilReadValues } = require('./register-value-utils') let state = controlState.createInitialState() let autoReadTimer = null let unsubscribeTransport = null const subscribers = [] function getState() { return { ...state } } function notify() { const nextState = getState() subscribers.slice().forEach((subscriber) => { subscriber(nextState) }) } function setState(changedData) { state = { ...state, ...changedData } notify() } function splitReadChunks(startAddress, quantity) { const chunks = [] let offset = 0 while (offset < quantity) { const chunkQuantity = Math.min(quantity - offset, MAX_READ_REGISTER_QUANTITY) chunks.push({ address: startAddress + offset, quantity: chunkQuantity }) offset += chunkQuantity } return chunks } async function readRegisterChunks(slaveAddress, functionCode, startAddress, quantity, label, expectedKind, options = {}) { const chunks = splitReadChunks(startAddress, quantity) const words = [] for (const chunk of chunks) { const response = await transport.sendManagedFrame( buildReadFrame(slaveAddress, functionCode, chunk.address, chunk.quantity), chunks.length > 1 ? `${label} ${chunk.address.toString(16).toUpperCase()}-${(chunk.address + chunk.quantity - 1).toString(16).toUpperCase()}` : label, { address: chunk.address, functionCode, kind: expectedKind, quantity: chunk.quantity, slaveAddress }, { showModal: options.showModal } ) if (!response) return null const chunkWords = response.words || [] chunkWords.forEach((word, index) => { words[chunk.address - startAddress + index] = Number(word) & 0xFFFF }) if (typeof options.onChunk === 'function') { options.onChunk(response, chunk) } } return words } function subscribe(subscriber) { if (typeof subscriber !== 'function') return () => {} subscribers.push(subscriber) subscriber(getState()) return () => { const index = subscribers.indexOf(subscriber) if (index >= 0) subscribers.splice(index, 1) } } function init() { transport.init() if (unsubscribeTransport) return unsubscribeTransport = transport.subscribe((transportState) => { const nextState = controlState.applyTransportState(state, transportState) if (nextState.autoReadStatus === false) { stopAutoReadStatus() } setState(nextState) }) } function syncSharedInputs() { controlState.setSharedInputValues(state.motorParameterInputRegisters) } function applyControlReadValues(coilValues) { setState(controlState.applyControlReadValues(state, coilValues)) } function applyMotorReadWords(words, startAddress = controlState.MOTOR_PARAM_START_ADDRESS) { const registerWordCache = controlState.getRegisterWordCache(startAddress, words) const motorState = controlState.applyMotorParameterReadValues(state, registerWordCache) const nextState = { ...state, ...motorState } setState({ ...motorState, ...controlState.applySpeedCommandReadValue(nextState, registerWordCache[0x68]) }) } function applyDriverReadWords(words) { setState(controlState.applyDriverParameterReadValues(state, words)) } function applyStatusReadWords(words, startAddress = controlState.STATUS_START_ADDRESS) { setState(controlState.applyStatusReadValues(words, startAddress)) } function getSharedSlaveAddress() { try { return transport.getSlaveAddress() } catch (error) { transport.showCommandAlert('从机地址错误', error.message) return null } } function updateMotorParameterInput(index, value) { setState(controlState.applyMotorParameterInput(state, index, value)) } function updateMotorParameterBlur(index, value) { setState(controlState.applyMotorParameterBlur(state, index, value)) } function updateSpeedCommandInput(value) { setState(controlState.applySpeedCommandInput(state, value)) } function updateSpeedCommandBlur(value) { setState(controlState.applySpeedCommandBlur(state, value)) sendSpeedCommand() } function getSpeedCommandWriteWord() { const writeValue = Number(state.speedCommand.writeValue) if (!Number.isFinite(writeValue)) return null const word = Math.round(writeValue) return word >= 0 && word <= 0xFFFF ? word : null } async function sendSpeedCommand() { const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return false const writeWord = getSpeedCommandWriteWord() if (writeWord === null) { transport.showCommandAlert('转速命令错误', '请检查转速命令输入值') return false } const address = parseInt(state.speedCommand.address, 16) const response = await transport.sendManagedFrame( buildWriteSingleRegisterFrame(slaveAddress, address, writeWord), '转速命令', { address, functionCode: 0x06, kind: 'speed-command-write', quantity: 1, value: writeWord, slaveAddress } ) if (response) { setState({ ...controlState.clearSpeedCommandDirty(state), systemTip: '转速命令已下发' }) return true } return false } async function sendControlCommand(key) { const button = state.controlButtons .concat(state.controlActionButtons || []) .find((item) => item.key === key) if (!button) return const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return const writeValue = controlState.getControlButtonWriteValue(button) const address = parseInt(button.address, 16) const coilEnabled = Number(writeValue) !== 0 const response = await transport.sendManagedFrame( buildWriteSingleCoilFrame(slaveAddress, address, coilEnabled), button.name, { address, functionCode: 0x05, kind: 'control-write', quantity: 1, value: coilEnabled ? 0xFF00 : 0x0000, slaveAddress } ) if (response) { setState(controlState.applyControlSuccess(state, button)) } } async function readControlStatus() { const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return false const startAddress = 0x00 const quantity = 3 const response = await transport.sendManagedFrame( buildReadFrame(slaveAddress, 0x01, startAddress, quantity), '控制状态读取', { address: startAddress, functionCode: 0x01, kind: 'control-status-read', quantity, slaveAddress } ) if (!response) return false const readValues = { coils: {} } addCoilReadValues(readValues, startAddress, quantity, response) setState({ ...controlState.applyControlReadValues(state, readValues.coils), systemTip: '控制状态读取完成' }) return true } async function readMotorParameters() { if (state.isReadingMotor) return const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return setState({ errorText: '', isReadingMotor: true, systemTip: '' }) try { const words = await readRegisterChunks( slaveAddress, 0x03, controlState.MOTOR_PARAM_START_ADDRESS, controlState.MOTOR_PARAM_WORD_COUNT, '电机参数读取', 'motor-main-read', { showModal: true } ) if (!words) return const registerWordCache = controlState.getRegisterWordCache(controlState.MOTOR_PARAM_START_ADDRESS, words) setState({ ...controlState.applyMotorParameterReadValues(state, registerWordCache), systemTip: '电机参数读取完成' }) } finally { setState({ isReadingMotor: false }) } } async function writeMotorParameters() { if (state.isWritingMotor) return const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return const mainWrite = controlState.buildMotorMainWriteValues(state) if (!mainWrite.values) { transport.showCommandAlert('参数错误', mainWrite.errorText) return } setState({ errorText: '', isWritingMotor: true, systemTip: '' }) try { const mainResponse = await transport.sendManagedFrame( buildWriteMultipleRegistersFrame( slaveAddress, controlState.MOTOR_PARAM_START_ADDRESS, mainWrite.values ), '电机参数写入', { address: controlState.MOTOR_PARAM_START_ADDRESS, functionCode: 0x10, kind: 'motor-main-write', quantity: mainWrite.values.length, slaveAddress } ) if (!mainResponse) return setState({ ...controlState.clearMotorParameterDirty(state), systemTip: '电机参数写入完成' }) } finally { setState({ isWritingMotor: false }) } } async function readDriverParameters() { if (state.isReadingDriver) return const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return setState({ errorText: '', isReadingDriver: true, systemTip: '' }) try { const words = await readRegisterChunks( slaveAddress, 0x04, controlState.DRIVER_PARAM_START_ADDRESS, controlState.DRIVER_PARAM_WORD_COUNT, '驱动器硬件参数读取', 'driver-read', { showModal: true } ) if (words) { setState({ ...controlState.applyDriverParameterReadValues(state, words), systemTip: '驱动器硬件参数读取完成' }) } } finally { setState({ isReadingDriver: false }) } } function setAutoReadStatus(autoReadStatus) { setState({ autoReadStatus }) if (autoReadStatus) { scheduleAutoReadStatus(0) return } stopAutoReadStatus() } function setAutoReadInterval(value) { const autoReadInterval = controlState.clampNumber( value, controlState.AUTO_READ_MIN_INTERVAL, controlState.AUTO_READ_MAX_INTERVAL, state.autoReadInterval ) setState({ autoReadInterval }) if (state.autoReadStatus) { scheduleAutoReadStatus(autoReadInterval) } } async function readStatus(options = {}) { if (options.auto && !state.connectedDevice) return false const slaveAddress = getSharedSlaveAddress() if (slaveAddress === null) return false const words = await readRegisterChunks( slaveAddress, 0x04, controlState.STATUS_START_ADDRESS, controlState.STATUS_WORD_COUNT, '状态读取', 'status-read', { showModal: !options.auto } ) if (words) { setState({ ...controlState.applyStatusReadValues(words, controlState.STATUS_START_ADDRESS), systemTip: options.auto ? '' : '状态读取完成' }) } return words } function scheduleAutoReadStatus(delay) { stopAutoReadStatus() autoReadTimer = setTimeout(async () => { if (!state.autoReadStatus) return await readStatus({ auto: true }) scheduleAutoReadStatus(state.autoReadInterval) }, delay) } function stopAutoReadStatus() { if (!autoReadTimer) return clearTimeout(autoReadTimer) autoReadTimer = null } module.exports = { getState, init, applyControlReadValues, applyDriverReadWords, applyMotorReadWords, applyStatusReadWords, readControlStatus, readDriverParameters, readMotorParameters, readStatus, sendControlCommand, sendSpeedCommand, setAutoReadInterval, setAutoReadStatus, stopAutoReadStatus, subscribe, syncSharedInputs, updateMotorParameterBlur, updateMotorParameterInput, updateSpeedCommandBlur, updateSpeedCommandInput, writeMotorParameters }