const { buildCodeInfoQueryFrame, buildDebugReadMemoryFrame, buildDebugWriteMemoryFrame, buildReadFrame, buildWriteMultipleRegistersFrame, buildWriteSingleCoilFrame, buildWriteSingleRegisterFrame, getMaxDebugReadByteLength, getMaxDebugWriteByteLength, getMaxReadQuantity, getMaxWriteMultipleRegisterQuantity } = require('./frame.js') const settingsService = require('../../store/settings-store.js') const transport = require('../../transport/ble-core.js') const { addCoilReadValues, addWordReadValues } = require('../../utils/register-value-utils.js') 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 isBroadcastAddress(slaveAddress) { return Number(slaveAddress) === 0 } function showBroadcastReadAlert(label) { transport.showCommandAlert(label || '读取命令错误', '广播地址 0x00 不支持读取') } 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) } function getDebugReadChunks(startAddress, byteLength, options = {}) { const maxByteLength = getMaxDebugReadByteLength(options.maxFrameBytes) return splitQuantity(startAddress, byteLength, maxByteLength || byteLength) } function getDebugWriteChunks(startAddress, bytes, options = {}) { const sourceBytes = Array.prototype.slice.call(bytes || []) const maxByteLength = getMaxDebugWriteByteLength(options.maxFrameBytes) const chunks = splitQuantity(startAddress, sourceBytes.length, maxByteLength || sourceBytes.length) let offset = 0 return chunks.map((chunk) => { const dataBytes = sourceBytes.slice(offset, offset + chunk.quantity) offset += chunk.quantity return { ...chunk, dataBytes } }) } async function sendReadChunk(slaveAddress, functionCode, chunk, label, kind, options = {}) { if (isBroadcastAddress(slaveAddress)) { showBroadcastReadAlert(label) return false } 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 sendDebugReadChunk(slaveAddress, memoryType, chunk, label, kind, options = {}) { if (isBroadcastAddress(slaveAddress)) { showBroadcastReadAlert(label) return false } return transport.sendManagedFrame( buildDebugReadMemoryFrame(slaveAddress, memoryType, chunk.address, chunk.quantity, { maxFrameBytes: options.maxFrameBytes }), label, { address: chunk.address, functionCode: 0x41, kind, memoryType, 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 readDebugMemory(slaveAddress, memoryType, startAddress, byteLength, label, kind = 'debug-memory-read', options = {}) { const bytes = [] const chunks = getDebugReadChunks(startAddress, byteLength, options) for (const chunk of chunks) { const response = await sendDebugReadChunk( slaveAddress, memoryType, chunk, getChunkLabel(label, chunks, chunk), kind, options ) if (!response) return null const dataBytes = Array.isArray(response.dataBytes) ? response.dataBytes : [] dataBytes.forEach((byte, index) => { bytes[chunk.address - startAddress + index] = Number(byte) & 0xFF }) if (typeof options.onChunk === 'function') { options.onChunk(response, chunk) } } return bytes } async function queryCodeInfoBlock(slaveAddress, label = '查询Code信息块', kind = 'code-info-query', options = {}) { if (isBroadcastAddress(slaveAddress)) { showBroadcastReadAlert(label) return null } const response = await transport.sendManagedFrame( buildCodeInfoQueryFrame(slaveAddress), label, { functionCode: 0x40, kind, quantity: 1, slaveAddress }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) if (!response) return null return { address: response.codeInfoAddress, byteLength: response.codeInfoLength, memoryType: response.memoryType } } async function readCodeInfoBlock(slaveAddress, label = '读取Code信息块', kind = 'code-info-read', options = {}) { const info = await queryCodeInfoBlock(slaveAddress, label, 'code-info-query', options) if (!info) return null if (info.memoryType !== 0x03) { transport.showCommandAlert(label, '设备返回的信息块不在 code 区') return null } const bytes = await readDebugMemory( slaveAddress, 0x03, info.address, info.byteLength, label, kind, options ) if (!bytes) return null return { ...info, bytes } } 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, isBroadcastAddress(slaveAddress) ? null : { 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, isBroadcastAddress(slaveAddress) ? null : { 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, isBroadcastAddress(slaveAddress) ? null : { address, functionCode: 0x10, kind, quantity: values.length, slaveAddress }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) } async function writeDebugMemory(slaveAddress, memoryType, startAddress, bytes, label, kind = 'debug-memory-write', options = {}) { const dataBytes = Array.prototype.slice.call(bytes || []).map((byte) => Number(byte) & 0xFF) const chunks = getDebugWriteChunks(startAddress, dataBytes, options) for (const chunk of chunks) { const response = await transport.sendManagedFrame( buildDebugWriteMemoryFrame(slaveAddress, memoryType, chunk.address, chunk.dataBytes, { maxFrameBytes: options.maxFrameBytes }), getChunkLabel(label, chunks, chunk), isBroadcastAddress(slaveAddress) ? null : { address: chunk.address, functionCode: 0x42, kind, memoryType, quantity: chunk.quantity, slaveAddress }, { maxFrameBytes: options.maxFrameBytes, showModal: options.showModal } ) if (!response) return false if (typeof options.onChunk === 'function') { options.onChunk(response, chunk) } } return true } module.exports = { getDebugReadChunks, getDebugWriteChunks, getReadChunks, getSharedSlaveAddress, getMaxReadQuantity, queryCodeInfoBlock, readBitValues, readCodeInfoBlock, readDebugMemory, readRegisterWords, readSingleHoldingWord, readSpans, splitQuantity, getMaxWriteMultipleRegisterQuantity, writeDebugMemory, writeMultipleRegisters, writeSingleCoil, writeSingleRegister }