const { normalizeTextValue } = require('../../utils/base-utils.js') const { bytesToWords, wordsToBytes } = require('../../utils/binary-utils.js') const { alignEvenByteLength, getBitFieldByteLength, getBitFieldMaxValue, getDataType, getDataTypeIndex, getEncodeByteLimit, getRegisterByteLength, getRegisterTextByteLength, getRegisterValueTypeLabel, getRegisterWordCount, getRegisterWordCountAtOffset, isBitFieldRegister, isBitRegisterType, isByteRegister, isHexRegister, isNumericRegister, isTextRegister, normalizeBitOffset, normalizeBitWidth, normalizeTextByteLength, supportsRange, supportsUnit } = require('./value-types.js') const { decodeTextBytes, encodeTextBytes } = require('./value-text.js') const { bytesToFloatValue, bytesToSignedInteger, bytesToUnsignedInteger, floatToBytes, formatFloatValue, formatHexValue, formatIntegerValue, parseNumberText, unsignedIntegerToBytes, validateNumericValue } = require('./value-number.js') function padWordHex(value) { return Number(value || 0).toString(16).toUpperCase().padStart(4, '0') } function formatRawWordText(words = []) { if (!Array.isArray(words) || !words.length) return '--' return words.map((word) => `0x${padWordHex(word)}`).join(' ') } function formatRawByteText(bytes = []) { if (!Array.isArray(bytes) || !bytes.length) return '--' return bytes.map((byte) => `0x${(Number(byte) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}`).join(' ') } function formatRawByteTextWithDefault(bytes = [], byteLength = 1) { const safeLength = Math.max(1, Math.floor(Number(byteLength) || 1)) const source = Array.isArray(bytes) ? bytes : [] return Array.from({ length: safeLength }, (_, index) => ( `0x${(Number(source[index] || 0) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}` )).join(' ') } function parseCoilValue(value) { const text = String(value === undefined || value === null ? '' : value).trim() if (!text || text === '--') return null if (['1', 'true', 'TRUE', 'on', 'ON', '开'].includes(text)) return 1 if (['0', 'false', 'FALSE', 'off', 'OFF', '关'].includes(text)) return 0 const coilValue = Number(text) return Number.isFinite(coilValue) ? (coilValue ? 1 : 0) : null } function parseBitFieldValue(register, valueText) { const text = normalizeTextValue(valueText).trim() const bitWidth = normalizeBitWidth(register.bitWidth) let parsed = parseNumberText(text, 'uint32_t') const maxValue = getBitFieldMaxValue(register) if (parsed === null && bitWidth === 1) parsed = parseCoilValue(text) if (parsed === null) return null if (Math.round(parsed) !== parsed || parsed < 0 || parsed > maxValue) { throw new Error(`${register.name || '位域'} 超出 0 - ${maxValue} 范围`) } return Math.round(parsed) } function decodeBitFieldBytes(register, bytes = []) { const bitOffset = normalizeBitOffset(register.bitOffset) const bitWidth = normalizeBitWidth(register.bitWidth) let byteIndex = 0 let currentBitOffset = bitOffset let multiplier = 1 let remaining = bitWidth let value = 0 while (remaining > 0 && byteIndex < bytes.length) { const take = Math.min(8 - currentBitOffset, remaining) const mask = (1 << take) - 1 const part = ((Number(bytes[byteIndex]) & 0xFF) >> currentBitOffset) & mask value += part * multiplier multiplier *= Math.pow(2, take) remaining -= take byteIndex += 1 currentBitOffset = 0 } return remaining > 0 ? null : value } function encodeBitFieldIntoBytes(register, bytes, byteStart = 0) { const valueText = normalizeTextValue(register.inputValue) let value = parseBitFieldValue(register, valueText) const bitOffset = normalizeBitOffset(register.bitOffset) const bitWidth = normalizeBitWidth(register.bitWidth) let byteIndex = Math.max(0, Math.floor(Number(byteStart) || 0)) let currentBitOffset = bitOffset let remaining = bitWidth if (value === null) return null while (remaining > 0) { const take = Math.min(8 - currentBitOffset, remaining) const mask = (1 << take) - 1 const shiftedMask = (mask << currentBitOffset) & 0xFF const part = value & mask if (byteIndex >= bytes.length) return null bytes[byteIndex] = ((Number(bytes[byteIndex]) & 0xFF) & (~shiftedMask & 0xFF)) | ((part << currentBitOffset) & shiftedMask) value = Math.floor(value / Math.pow(2, take)) remaining -= take byteIndex += 1 currentBitOffset = 0 } return bytes } function encodeBitFieldBytes(register) { const bytes = Array.from({ length: getBitFieldByteLength(register) }, () => 0) return encodeBitFieldIntoBytes(register, bytes, 0) } function getRegisterDataBytes(register, words) { const dataType = getDataType(register.dataType).key const byteLength = getRegisterByteLength(dataType, register) const byteOffset = Math.max(0, Math.floor(Number(register.byteOffset) || 0)) const sourceBytes = wordsToBytes(words, Math.max(0, (Array.isArray(words) ? words.length : 0) * 2)) return sourceBytes.slice(byteOffset, byteOffset + byteLength) } function encodeRegisterBytes(register) { const dataType = getDataType(register.dataType).key const valueText = normalizeTextValue(register.inputValue) const byteLength = getRegisterByteLength(dataType, register) if (isBitFieldRegister(register)) { return encodeBitFieldBytes(register) } if (isTextRegister(dataType)) { const byteLimit = getEncodeByteLimit(register) const bytes = encodeTextBytes(valueText, dataType, byteLimit) const paddedBytes = bytes.slice() while (paddedBytes.length < byteLength) { paddedBytes.push(0) } return paddedBytes.slice(0, byteLength) } const numberValue = parseNumberText(valueText, dataType) if (numberValue === null) return null validateNumericValue(register, numberValue) if (dataType === 'float') return floatToBytes(numberValue) const rounded = Math.round(numberValue) if (dataType === 'int8_t' || dataType === 'uint8_t') return [rounded & 0xFF] if (dataType === 'int16_t' || dataType === 'uint16_t' || dataType === 'hex') { return unsignedIntegerToBytes(rounded, 2) } if (dataType === 'int32_t' || dataType === 'uint32_t') { return unsignedIntegerToBytes(rounded, 4) } return unsignedIntegerToBytes(rounded, byteLength) } function encodeRegisterWords(register) { const dataType = getDataType(register.dataType).key const bytes = encodeRegisterBytes(register) if (!Array.isArray(bytes)) return null if (isByteRegister(dataType)) return [bytes[0] & 0xFF] return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0)) } function decodeRegisterValue(register, words) { const dataType = getDataType(register.dataType).key if (!Array.isArray(words) || words.length < getRegisterWordCountAtOffset(dataType, register.byteOffset || 0, register)) return null const bytes = getRegisterDataBytes(register, words) const byteLength = getRegisterByteLength(dataType, register) if (bytes.length < byteLength) return null if (isBitFieldRegister(register)) { return decodeBitFieldBytes(register, bytes) } if (isTextRegister(dataType)) { return decodeTextBytes(bytes.slice(0, getEncodeByteLimit(register)), dataType) } if (dataType === 'float') { return bytesToFloatValue(bytes) } if (dataType === 'int8_t') { const byteValue = bytes[0] & 0xFF return byteValue & 0x80 ? byteValue - 0x100 : byteValue } if (dataType === 'uint8_t') { return bytes[0] & 0xFF } if (dataType === 'int16_t') { return bytesToSignedInteger(bytes.slice(0, 2)) } if (dataType === 'uint16_t') { return bytesToUnsignedInteger(bytes.slice(0, 2)) } if (dataType === 'hex') { return bytesToUnsignedInteger(bytes.slice(0, 2)) } if (dataType === 'int32_t') { return bytesToSignedInteger(bytes.slice(0, 4)) } return bytesToUnsignedInteger(bytes.slice(0, 4)) } function formatRegisterValue(register, rawValue) { if (rawValue === null || rawValue === undefined) return '--' const dataType = getDataType(register.dataType).key if (isTextRegister(dataType)) return normalizeTextValue(rawValue) if (dataType === 'hex') return formatHexValue(rawValue) if (dataType === 'float') return formatFloatValue(rawValue) return formatIntegerValue(rawValue, dataType) } function formatCoilDisplayValue(value) { return Number(value) ? '1' : '0' } function registerTypeIsBit(register) { return !!register && isBitRegisterType(register.registerType) } function validateRegisterValue(register, value) { const valueText = normalizeTextValue( value === undefined || value === null ? '' : value ).trim() if (!valueText || valueText === '--') return true if (registerTypeIsBit(register)) { if (parseCoilValue(valueText) === null) { throw new Error(`${register.name || '线圈'} 只能填写 0 或 1`) } return true } if (isBitFieldRegister(register)) { if (parseBitFieldValue(register, valueText) === null) { throw new Error(`${register.name || '位域'} 输入值无效`) } return true } const dataType = getDataType(register.dataType).key if (isTextRegister(dataType)) { encodeTextBytes(valueText, dataType, getEncodeByteLimit(register)) return true } const numberValue = parseNumberText(valueText, dataType) if (numberValue === null) { throw new Error(`${register.name || '寄存器'} 输入值无效`) } return validateNumericValue(register, numberValue) } module.exports = { alignEvenByteLength, decodeRegisterValue, encodeBitFieldIntoBytes, encodeRegisterBytes, encodeRegisterWords, encodeTextBytes, formatCoilDisplayValue, formatRawByteText, formatRawByteTextWithDefault, formatRawWordText, formatRegisterValue, getBitFieldByteLength, getBitFieldMaxValue, getDataType, getDataTypeIndex, getEncodeByteLimit, getRegisterByteLength, getRegisterTextByteLength, getRegisterValueTypeLabel, getRegisterWordCount, getRegisterWordCountAtOffset, isBitFieldRegister, isBitRegisterType, isByteRegister, isHexRegister, isNumericRegister, isTextRegister, normalizeBitOffset, normalizeBitWidth, normalizeTextByteLength, parseCoilValue, parseNumberText, registerTypeIsBit, supportsRange, supportsUnit, validateRegisterValue }