const { normalizeTextValue } = require('../../utils/base-utils.js') const { bytesToWords } = require('../../utils/binary-utils.js') const { MAX_MODBUS_ADDRESS } = require('./constants.js') const { decodeRegisterValue, encodeBitFieldIntoBytes, encodeRegisterBytes, encodeRegisterWords, isBitFieldRegister, validateRegisterValue: validateCodecRegisterValue } = require('./value-codec.js') function normalizeAddress(value, fallback = 0) { if (typeof value === 'number') { return Number.isFinite(value) ? Math.max(0, Math.min(Math.round(value), MAX_MODBUS_ADDRESS)) : fallback } const text = String(value === undefined || value === null ? '' : value).trim() if (!text) return fallback const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text if (/^[0-9A-F]+$/i.test(hexText)) { const parsedHex = parseInt(hexText, 16) return Number.isFinite(parsedHex) ? Math.max(0, Math.min(parsedHex, MAX_MODBUS_ADDRESS)) : fallback } const numberValue = Number(text) return Number.isFinite(numberValue) ? Math.max(0, Math.min(Math.round(numberValue), MAX_MODBUS_ADDRESS)) : fallback } function splitWordSpans(startAddress, quantity, maxQuantity) { const spans = [] let address = normalizeAddress(startAddress, 0) let remaining = Math.max(0, Math.floor(Number(quantity) || 0)) while (remaining > 0) { const spanQuantity = Math.min(remaining, maxQuantity) spans.push({ address, quantity: spanQuantity }) address += spanQuantity remaining -= spanQuantity } return spans } function getRegisterWriteValueText(register) { if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue) if (register.defaultValue !== undefined && register.defaultValue !== null) return normalizeTextValue(register.defaultValue) return '' } function getRegisterWordsFromWordCache(register, wordCache) { const words = [] for (let offset = 0; offset < register.registerCount; offset += 1) { const word = wordCache[register.address + offset] if (word === undefined) return null words.push(word) } return words } function getRegisterBytesFromByteCache(register, byteCache) { const bytes = [] for (let offset = 0; offset < register.byteLength; offset += 1) { const byte = byteCache[register.address + offset] if (byte === undefined) return null bytes.push(Number(byte) & 0xFF) } return bytes } function getRegisterWordsFromByteCache(register, byteCache) { const bytes = getRegisterBytesFromByteCache(register, byteCache) if (!bytes) return null return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0)) } function decodeRegisterFromBytes(register, bytes) { if (!Array.isArray(bytes) || bytes.length < register.byteLength) return null return decodeRegisterValue( { ...register, byteOffset: 0 }, bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0)) ) } function decodeRegisterFromWordCache(register, wordCache) { const words = getRegisterWordsFromWordCache(register, wordCache) if (!words) return null return decodeRegisterValue(register, words) } function decodeRegisterFromByteCache(register, byteCache) { const bytes = getRegisterBytesFromByteCache(register, byteCache) if (!bytes) return null return decodeRegisterFromBytes(register, bytes) } function getRegisterEncodedWords(register) { return encodeRegisterWords({ ...register, inputValue: getRegisterWriteValueText(register) }) } function getRegisterEncodedBytes(register) { return encodeRegisterBytes({ ...register, inputValue: getRegisterWriteValueText(register) }) } function getRegisterByteStart(register, groupStartAddress = 0) { if (Number.isFinite(Number(register.byteStart))) { return Math.max(0, Math.floor(Number(register.byteStart))) } return Math.max(0, ((Number(register.address) || 0) - (Number(groupStartAddress) || 0)) * 2 + (Number(register.byteOffset) || 0)) } function getGroupEncodedBytes(group, baseBytes = null) { const byteLength = Math.max(2, Number(group && group.paddedByteLength) || ((Number(group && group.wordQuantity) || 1) * 2)) const bytes = Array.isArray(baseBytes) ? baseBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF) : [] const registers = Array.isArray(group && group.registers) ? group.registers : [] while (bytes.length < byteLength) { bytes.push(0) } registers.forEach((register) => { const byteStart = getRegisterByteStart(register, group.startAddress) if (isBitFieldRegister(register)) { const encodedBytes = encodeBitFieldIntoBytes( { ...register, inputValue: getRegisterWriteValueText(register) }, bytes, byteStart ) if (!Array.isArray(encodedBytes)) { throw new Error(`${register.name || '位域'} 没有有效写入值`) } return } const registerBytes = encodeRegisterBytes(register) if (!Array.isArray(registerBytes) || !registerBytes.length) { throw new Error(`${register.name || '寄存器'} 没有有效写入值`) } for (let offset = 0; offset < register.byteLength; offset += 1) { if (byteStart + offset < bytes.length) { bytes[byteStart + offset] = Number(registerBytes[offset] || 0) & 0xFF } } }) return bytes } function getGroupEncodedWords(group, baseBytes = null) { return bytesToWords(getGroupEncodedBytes(group, baseBytes)) } function validateRegisterValue(register, value) { return validateCodecRegisterValue( register, value === undefined || value === null ? getRegisterWriteValueText(register) : value ) } module.exports = { decodeRegisterFromByteCache, decodeRegisterFromBytes, decodeRegisterFromWordCache, getGroupEncodedBytes, getGroupEncodedWords, getRegisterBytesFromByteCache, getRegisterEncodedBytes, getRegisterEncodedWords, getRegisterWordsFromByteCache, getRegisterWordsFromWordCache, getRegisterWriteValueText, splitWordSpans, validateRegisterValue }