const { buildReadFrame, buildWriteMultipleRegistersFrame, buildWriteSingleCoilFrame, buildWriteSingleRegisterFrame, getMaxReadQuantity, getMaxWriteMultipleRegisterQuantity } = require('./modbus-rtu') const settingsService = require('./settings-service') const transport = require('./ble-transport') const { addCoilReadValues, addWordReadValues } = require('./register-value-utils') function getSharedSlaveAddress(title = '从机地址错误') { try { return settingsService.getModbusSlaveAddress() } catch (error) { transport.showCommandAlert(title, error.message) return null } } function formatAddress(value) { return Number(value || 0).toString(16).toUpperCase() } function getChunkLabel(label, chunks, chunk) { if (!label || chunks.length <= 1) return label return `${label} ${formatAddress(chunk.address)}-${formatAddress(chunk.address + chunk.quantity - 1)}` } function splitQuantity(startAddress, quantity, maxQuantity) { const chunks = [] let address = Number(startAddress) || 0 let remaining = Math.max(0, Math.floor(Number(quantity) || 0)) const chunkLimit = Math.max(1, Math.floor(Number(maxQuantity) || remaining || 1)) while (remaining > 0) { const chunkQuantity = Math.min(remaining, chunkLimit) chunks.push({ address, quantity: chunkQuantity }) address += chunkQuantity remaining -= chunkQuantity } return chunks } function getReadChunks(functionCode, startAddress, quantity, options = {}) { const maxQuantity = getMaxReadQuantity(functionCode, options.maxFrameBytes) return splitQuantity(startAddress, quantity, maxQuantity || quantity) } async function sendReadChunk(slaveAddress, functionCode, chunk, label, kind, options = {}) { return transport.sendManagedFrame( buildReadFrame(slaveAddress, functionCode, chunk.address, chunk.quantity, { maxFrameBytes: options.maxFrameBytes }), label, { address: chunk.address, functionCode, kind, quantity: chunk.quantity, slaveAddress }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) } async function readSpans(slaveAddress, functionCode, spans, label, kind, options = {}) { const readValues = { coils: {}, words: {} } const normalizedSpans = (spans || []).filter((span) => span && span.quantity > 0) for (const span of normalizedSpans) { const chunks = getReadChunks(functionCode, span.address, span.quantity, options) for (const chunk of chunks) { const response = await sendReadChunk( slaveAddress, functionCode, chunk, getChunkLabel(label, chunks, chunk), kind, options ) if (!response) return null if (functionCode === 0x01 || functionCode === 0x02) { addCoilReadValues(readValues, chunk.address, chunk.quantity, response) } else { addWordReadValues(readValues, chunk.address, response) } if (typeof options.onChunk === 'function') { options.onChunk(response, chunk) } } } return readValues } async function readRegisterWords(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) { const words = [] const chunks = getReadChunks(functionCode, startAddress, quantity, options) for (const chunk of chunks) { const response = await sendReadChunk( slaveAddress, functionCode, chunk, getChunkLabel(label, chunks, chunk), kind, options ) 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 } async function readBitValues(slaveAddress, functionCode, startAddress, quantity, label, kind, options = {}) { const result = await readSpans( slaveAddress, functionCode, [{ address: startAddress, quantity }], label, kind, options ) return result ? result.coils : null } async function readSingleHoldingWord(slaveAddress, address, label = '读取配对寄存器', kind = 'holding-word-read') { const words = await readRegisterWords(slaveAddress, 0x03, address, 1, label, kind) return words && Number.isInteger(words[0]) ? words[0] & 0xFFFF : null } function writeSingleCoil(slaveAddress, address, checked, label, kind = 'coil-write', options = {}) { const coilValue = checked ? 0xFF00 : 0x0000 return transport.sendManagedFrame( buildWriteSingleCoilFrame(slaveAddress, address, checked), label, { address, functionCode: 0x05, kind, quantity: 1, slaveAddress, value: coilValue }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) } function writeSingleRegister(slaveAddress, address, value, label, kind = 'register-write', options = {}) { return transport.sendManagedFrame( buildWriteSingleRegisterFrame(slaveAddress, address, value), label, { address, functionCode: 0x06, kind, quantity: 1, slaveAddress, value }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) } function writeMultipleRegisters(slaveAddress, address, values, label, kind = 'registers-write', options = {}) { return transport.sendManagedFrame( buildWriteMultipleRegistersFrame(slaveAddress, address, values, { maxFrameBytes: options.maxFrameBytes }), label, { address, functionCode: 0x10, kind, quantity: values.length, slaveAddress }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) } module.exports = { getReadChunks, getSharedSlaveAddress, getMaxReadQuantity, readBitValues, readRegisterWords, readSingleHoldingWord, readSpans, splitQuantity, getMaxWriteMultipleRegisterQuantity, writeMultipleRegisters, writeSingleCoil, writeSingleRegister }