|
|
@@ -122,6 +122,36 @@ const DEFAULT_REGISTER_TYPE = REGISTER_TYPE_OPTIONS[0].key
|
|
|
const DEFAULT_DATA_TYPE = 'uint16_t'
|
|
|
const GROUP_LAYOUT_REGISTER = 'register'
|
|
|
const GROUP_LAYOUT_STRUCT = 'struct'
|
|
|
+const BYTE_ADDRESS_MEMORY_AREAS = ['BIT', 'CODE', 'DATA', 'IDATA', 'XDATA']
|
|
|
+const SOURCE_REGISTER_FIELDS = [
|
|
|
+ 'sourceAddress',
|
|
|
+ 'sourceAddressText',
|
|
|
+ 'sourceByteLength',
|
|
|
+ 'sourceBitOffset',
|
|
|
+ 'sourceBitWidth',
|
|
|
+ 'sourceMemoryArea',
|
|
|
+ 'sourceMemoryClass',
|
|
|
+ 'sourceSymbolName',
|
|
|
+ 'sourceSymbolType'
|
|
|
+]
|
|
|
+const STRUCT_REGISTER_FIELDS = [
|
|
|
+ 'bitOffset',
|
|
|
+ 'bitWidth',
|
|
|
+ 'byteStart',
|
|
|
+ 'isBitField',
|
|
|
+ 'structByteLength'
|
|
|
+]
|
|
|
+const SOURCE_GROUP_FIELDS = [
|
|
|
+ 'addressUnit',
|
|
|
+ 'sourceAddress',
|
|
|
+ 'sourceAddressText',
|
|
|
+ 'sourceByteLength',
|
|
|
+ 'sourceMemoryArea',
|
|
|
+ 'sourceMemoryClass',
|
|
|
+ 'sourceSegment',
|
|
|
+ 'sourceSegmentModule',
|
|
|
+ 'sourceSymbolName'
|
|
|
+]
|
|
|
|
|
|
function normalizeAddress(value, fallback = 0) {
|
|
|
if (typeof value === 'number') {
|
|
|
@@ -206,7 +236,38 @@ function isStructLayout(layout) {
|
|
|
return layout === GROUP_LAYOUT_STRUCT
|
|
|
}
|
|
|
|
|
|
+function isBitFieldRegister(register = {}) {
|
|
|
+ return !!register.isBitField
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeBitOffset(value) {
|
|
|
+ const numberValue = Math.floor(Number(value) || 0)
|
|
|
+
|
|
|
+ return Math.min(Math.max(numberValue, 0), 7)
|
|
|
+}
|
|
|
+
|
|
|
+function normalizeBitWidth(value) {
|
|
|
+ const numberValue = Math.round(Number(value) || 1)
|
|
|
+
|
|
|
+ return Math.min(Math.max(numberValue, 1), 32)
|
|
|
+}
|
|
|
+
|
|
|
+function getBitFieldByteLength(register = {}) {
|
|
|
+ const bitOffset = normalizeBitOffset(register.bitOffset)
|
|
|
+ const bitWidth = normalizeBitWidth(register.bitWidth)
|
|
|
+
|
|
|
+ return Math.max(1, Math.ceil((bitOffset + bitWidth) / 8))
|
|
|
+}
|
|
|
+
|
|
|
+function getBitFieldMaxValue(register = {}) {
|
|
|
+ const bitWidth = normalizeBitWidth(register.bitWidth)
|
|
|
+
|
|
|
+ return bitWidth >= 32 ? 0xFFFFFFFF : Math.pow(2, bitWidth) - 1
|
|
|
+}
|
|
|
+
|
|
|
function getRegisterByteLength(dataType, register = {}) {
|
|
|
+ if (isBitFieldRegister(register)) return getBitFieldByteLength(register)
|
|
|
+
|
|
|
const type = getDataType(dataType)
|
|
|
if (type.kind === 'text') {
|
|
|
const byteLength = getRegisterTextByteLength(register)
|
|
|
@@ -221,9 +282,13 @@ function getRegisterWordCount(dataType, register = {}) {
|
|
|
return Math.max(1, Math.ceil(getRegisterByteLength(dataType, register) / 2))
|
|
|
}
|
|
|
|
|
|
+function getByteSpanWordCount(byteOffset, byteLength) {
|
|
|
+ return Math.max(1, Math.ceil((Math.max(0, Number(byteOffset) || 0) + Math.max(1, Number(byteLength) || 1)) / 2))
|
|
|
+}
|
|
|
+
|
|
|
function getRegisterWordCountAtOffset(dataType, byteOffset, register = {}) {
|
|
|
const byteLength = getRegisterByteLength(dataType, register)
|
|
|
- return Math.max(1, Math.ceil((byteOffset + byteLength) / 2))
|
|
|
+ return getByteSpanWordCount(byteOffset, byteLength)
|
|
|
}
|
|
|
|
|
|
function getEncodeByteLimit(register) {
|
|
|
@@ -270,6 +335,12 @@ function formatRawWordText(words = []) {
|
|
|
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 formatAddressRange(startAddress, wordCount) {
|
|
|
const address = normalizeAddress(startAddress, 0)
|
|
|
const count = Math.max(1, Number(wordCount) || 1)
|
|
|
@@ -282,6 +353,14 @@ function formatAddressRange(startAddress, wordCount) {
|
|
|
return `0x${padWordHex(address)}-0x${padWordHex(safeEndAddress)}${overflowText}`
|
|
|
}
|
|
|
|
|
|
+function isByteAddressedGroup(group = {}) {
|
|
|
+ const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()
|
|
|
+
|
|
|
+ return group.addressUnit === 'byte'
|
|
|
+ || group.addressUnit === 'bytes'
|
|
|
+ || BYTE_ADDRESS_MEMORY_AREAS.indexOf(memoryArea) >= 0
|
|
|
+}
|
|
|
+
|
|
|
function formatRegisterAddressText(address, byteOffset, byteLength, registerType) {
|
|
|
if (isBitRegisterType(registerType)) return `0x${padHex(address)}`
|
|
|
if (byteLength === 1) return `0x${padHex(address)}${byteOffset === 0 ? 'H' : 'L'}`
|
|
|
@@ -289,6 +368,16 @@ function formatRegisterAddressText(address, byteOffset, byteLength, registerType
|
|
|
return `0x${padHex(address)}`
|
|
|
}
|
|
|
|
|
|
+function formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth) {
|
|
|
+ const byteText = formatRegisterAddressText(address, byteOffset, 1, DEFAULT_REGISTER_TYPE)
|
|
|
+ const startBit = normalizeBitOffset(bitOffset)
|
|
|
+ const endBit = startBit + normalizeBitWidth(bitWidth) - 1
|
|
|
+
|
|
|
+ return endBit === startBit
|
|
|
+ ? `${byteText}.b${startBit}`
|
|
|
+ : `${byteText}.b${startBit}..${endBit}`
|
|
|
+}
|
|
|
+
|
|
|
function isAddressRangeOverflow(startAddress, wordCount) {
|
|
|
const address = normalizeAddress(startAddress, 0)
|
|
|
const count = Math.max(1, Number(wordCount) || 1)
|
|
|
@@ -583,6 +672,81 @@ function bytesToSignedInteger(bytes) {
|
|
|
return unsignedValue >= signLimit ? unsignedValue - fullRange : unsignedValue
|
|
|
}
|
|
|
|
|
|
+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)
|
|
|
@@ -597,6 +761,10 @@ function encodeRegisterBytes(register) {
|
|
|
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)
|
|
|
@@ -640,12 +808,16 @@ function encodeRegisterWords(register) {
|
|
|
function decodeRegisterValue(register, words) {
|
|
|
const dataType = getDataType(register.dataType).key
|
|
|
|
|
|
- if (!Array.isArray(words) || words.length < getRegisterWordCount(dataType, register)) return null
|
|
|
+ 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)
|
|
|
}
|
|
|
@@ -704,10 +876,49 @@ function normalizeRegisterDataType(register, registerType) {
|
|
|
return getDataType(register.dataType || register.type || DEFAULT_DATA_TYPE).key
|
|
|
}
|
|
|
|
|
|
+function pickFields(source, fields) {
|
|
|
+ return fields.reduce((result, field) => {
|
|
|
+ if (source && source[field] !== undefined && source[field] !== null && source[field] !== '') {
|
|
|
+ result[field] = source[field]
|
|
|
+ }
|
|
|
+
|
|
|
+ return result
|
|
|
+ }, {})
|
|
|
+}
|
|
|
+
|
|
|
+function createRegisterSourceMetaText(register) {
|
|
|
+ const bitText = isBitFieldRegister(register)
|
|
|
+ ? `bit${normalizeBitOffset(register.bitOffset)}:${normalizeBitWidth(register.bitWidth)}`
|
|
|
+ : ''
|
|
|
+ const parts = [
|
|
|
+ register.sourceMemoryArea,
|
|
|
+ register.sourceAddressText,
|
|
|
+ bitText,
|
|
|
+ register.sourceSymbolType && register.sourceSymbolType !== '---' ? register.sourceSymbolType : ''
|
|
|
+ ].filter(Boolean)
|
|
|
+
|
|
|
+ return parts.join(' · ')
|
|
|
+}
|
|
|
+
|
|
|
+function createGroupSourceMetaText(group) {
|
|
|
+ const parts = [
|
|
|
+ group.sourceMemoryArea,
|
|
|
+ group.sourceAddressText,
|
|
|
+ group.sourceSymbolName,
|
|
|
+ group.sourceSegmentModule
|
|
|
+ ].filter(Boolean)
|
|
|
+
|
|
|
+ return parts.join(' · ')
|
|
|
+}
|
|
|
+
|
|
|
function normalizeRegister(register, group, index, address, byteOffset = 0) {
|
|
|
const registerType = getRegisterType(group.registerType).key
|
|
|
const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
|
|
|
+ const byteAddressed = isByteAddressedGroup(group)
|
|
|
const dataType = normalizeRegisterDataType(register, registerType)
|
|
|
+ const bitOffset = normalizeBitOffset(register.bitOffset)
|
|
|
+ const bitWidth = normalizeBitWidth(register.bitWidth)
|
|
|
+ const isBitField = !isBitRegisterType(registerType) && isBitFieldRegister(register)
|
|
|
const textByteLength = isTextRegister(dataType)
|
|
|
? normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
|
|
|
: ''
|
|
|
@@ -715,15 +926,24 @@ function normalizeRegister(register, group, index, address, byteOffset = 0) {
|
|
|
const savedValue = getRegisterSavedValue(register)
|
|
|
const inputValue = savedValue === null ? defaultValue : savedValue
|
|
|
const rawValue = register.rawValue === undefined ? null : register.rawValue
|
|
|
- const byteLength = isBitRegisterType(registerType) ? 1 : getRegisterByteLength(dataType, { layout, textByteLength })
|
|
|
- const registerCount = isBitRegisterType(registerType) ? 1 : getRegisterWordCountAtOffset(dataType, byteOffset, { layout, textByteLength })
|
|
|
- const canShowUnit = !isBitRegisterType(registerType) && supportsUnit(dataType)
|
|
|
+ const byteLength = isBitRegisterType(registerType)
|
|
|
+ ? 1
|
|
|
+ : getRegisterByteLength(dataType, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
|
|
|
+ const registerCount = isBitRegisterType(registerType)
|
|
|
+ ? 1
|
|
|
+ : getRegisterWordCountAtOffset(dataType, byteOffset, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
|
|
|
+ const canShowUnit = !isBitRegisterType(registerType) && !isBitField && supportsUnit(dataType)
|
|
|
const rawWords = Array.isArray(register.rawWords)
|
|
|
? register.rawWords.slice(0, registerCount).map((word) => Number(word) & 0xFFFF)
|
|
|
: []
|
|
|
+ const rawBytes = Array.isArray(register.rawBytes)
|
|
|
+ ? register.rawBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF)
|
|
|
+ : []
|
|
|
const rawValueText = rawValue === null
|
|
|
? '--'
|
|
|
- : (isBitRegisterType(registerType) ? formatCoilDisplayValue(rawValue) : formatRawWordText(rawWords))
|
|
|
+ : (isBitRegisterType(registerType)
|
|
|
+ ? formatCoilDisplayValue(rawValue)
|
|
|
+ : (byteAddressed ? formatRawByteText(rawBytes) : formatRawWordText(rawWords)))
|
|
|
const displayValue = rawValue === null
|
|
|
? (inputValue.trim() ? inputValue : '--')
|
|
|
: formatRegisterValue({ ...register, dataType, byteOffset }, rawValue)
|
|
|
@@ -732,12 +952,24 @@ function normalizeRegister(register, group, index, address, byteOffset = 0) {
|
|
|
address,
|
|
|
addressRangeText: isBitRegisterType(registerType)
|
|
|
? `0x${padHex(address)}`
|
|
|
- : formatAddressRange(address, registerCount),
|
|
|
- addressText: formatRegisterAddressText(address, byteOffset, byteLength, registerType),
|
|
|
+ : (byteAddressed
|
|
|
+ ? formatAddressRange(address, Math.max(1, byteLength))
|
|
|
+ : formatAddressRange(address, registerCount)),
|
|
|
+ addressText: isBitField
|
|
|
+ ? (byteAddressed
|
|
|
+ ? `${formatAddressRange(address, Math.max(1, byteLength))}.b${bitOffset}${bitWidth > 1 ? `..b${bitOffset + bitWidth - 1}` : ''}`
|
|
|
+ : formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth))
|
|
|
+ : (byteAddressed
|
|
|
+ ? formatAddressRange(address, Math.max(1, byteLength))
|
|
|
+ : formatRegisterAddressText(address, byteOffset, byteLength, registerType)),
|
|
|
+ bitOffset: isBitField ? bitOffset : '',
|
|
|
+ bitWidth: isBitField ? bitWidth : '',
|
|
|
byteLength,
|
|
|
byteLengthText: isBitRegisterType(registerType)
|
|
|
? '1bit'
|
|
|
- : (textByteLength && textByteLength !== byteLength ? `${textByteLength}B/占${byteLength}B` : `${byteLength}B`),
|
|
|
+ : (isBitField
|
|
|
+ ? (bitWidth === 1 ? `1bit/占${byteLength}B` : `${bitWidth}bit/占${byteLength}B`)
|
|
|
+ : (textByteLength && textByteLength !== byteLength ? `${textByteLength}B/占${byteLength}B` : `${byteLength}B`)),
|
|
|
byteStart: Math.max(0, Math.floor(Number(register.byteStart) || 0)),
|
|
|
dataType,
|
|
|
dataTypeIndex: getDataTypeIndex(dataType),
|
|
|
@@ -749,23 +981,28 @@ function normalizeRegister(register, group, index, address, byteOffset = 0) {
|
|
|
inputValue,
|
|
|
isStructField: layout === GROUP_LAYOUT_STRUCT || !!register.isStructField,
|
|
|
layout,
|
|
|
+ isBitField,
|
|
|
isDirty: !!register.isDirty,
|
|
|
maxValue: normalizeTextValue(register.maxValue),
|
|
|
minValue: normalizeTextValue(register.minValue),
|
|
|
name: register.name || `寄存器 ${index + 1}`,
|
|
|
rawValue,
|
|
|
rawValueText,
|
|
|
+ rawBytes,
|
|
|
rawWords,
|
|
|
registerCount,
|
|
|
byteOffset,
|
|
|
registerType,
|
|
|
showDataType: !isBitRegisterType(registerType),
|
|
|
- showRange: !isBitRegisterType(registerType) && supportsRange(dataType),
|
|
|
+ showRange: !isBitRegisterType(registerType) && !isBitField && supportsRange(dataType),
|
|
|
showTextLength: !isBitRegisterType(registerType) && isTextRegister(dataType),
|
|
|
showUnit: canShowUnit,
|
|
|
textByteLength,
|
|
|
unit: canShowUnit ? normalizeTextValue(register.unit).trim() : '',
|
|
|
- remark: register.remark || ''
|
|
|
+ structByteLength: register.structByteLength,
|
|
|
+ remark: register.remark || '',
|
|
|
+ ...pickFields(register, SOURCE_REGISTER_FIELDS),
|
|
|
+ sourceMetaText: createRegisterSourceMetaText(register)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -775,6 +1012,7 @@ function normalizeGroup(group) {
|
|
|
const maxQuantity = getMaxQuantity(registerType.key)
|
|
|
const sourceRegisters = Array.isArray(group.registers) ? group.registers : []
|
|
|
const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
|
|
|
+ const byteAddressed = isByteAddressedGroup(group)
|
|
|
const hasExplicitQuantity = group.quantity !== undefined && group.quantity !== null && group.quantity !== ''
|
|
|
const quantity = hasExplicitQuantity
|
|
|
? clampInteger(group.quantity, 1, maxQuantity, 1)
|
|
|
@@ -789,7 +1027,8 @@ function normalizeGroup(group) {
|
|
|
quantity,
|
|
|
registerType: registerType.key,
|
|
|
startAddress,
|
|
|
- touchStartX: 0
|
|
|
+ touchStartX: 0,
|
|
|
+ ...pickFields(group, SOURCE_GROUP_FIELDS)
|
|
|
}
|
|
|
const registers = []
|
|
|
let nextAddress = startAddress
|
|
|
@@ -807,18 +1046,24 @@ function normalizeGroup(group) {
|
|
|
let byteOffset = 0
|
|
|
|
|
|
if (!isBitRegister) {
|
|
|
- const byteLength = getRegisterByteLength(dataType, { layout, textByteLength })
|
|
|
+ const explicitByteStart = Number(sourceRegister.byteStart)
|
|
|
+ const hasExplicitByteStart = layout === GROUP_LAYOUT_STRUCT && Number.isFinite(explicitByteStart)
|
|
|
+ const byteLength = getRegisterByteLength(dataType, { ...sourceRegister, layout, textByteLength })
|
|
|
if (layout !== GROUP_LAYOUT_STRUCT && !isByteRegister(dataType) && nextByteOffset % 2 !== 0) {
|
|
|
nextByteOffset += 1
|
|
|
}
|
|
|
|
|
|
- address = startAddress + Math.floor(nextByteOffset / 2)
|
|
|
- byteOffset = nextByteOffset % 2
|
|
|
+ const currentByteStart = hasExplicitByteStart
|
|
|
+ ? Math.max(0, Math.floor(explicitByteStart))
|
|
|
+ : nextByteOffset
|
|
|
+
|
|
|
+ address = byteAddressed ? startAddress + currentByteStart : startAddress + Math.floor(currentByteStart / 2)
|
|
|
+ byteOffset = byteAddressed ? 0 : currentByteStart % 2
|
|
|
normalizedSourceRegister = {
|
|
|
...sourceRegister,
|
|
|
- byteStart: nextByteOffset
|
|
|
+ byteStart: currentByteStart
|
|
|
}
|
|
|
- nextByteOffset += byteLength
|
|
|
+ nextByteOffset = Math.max(nextByteOffset, currentByteStart + byteLength)
|
|
|
}
|
|
|
|
|
|
const register = normalizeRegister(normalizedSourceRegister, baseGroup, index, address, byteOffset)
|
|
|
@@ -831,16 +1076,17 @@ function normalizeGroup(group) {
|
|
|
: Math.max(1, nextByteOffset)
|
|
|
const paddedByteLength = isBitRegisterType(baseGroup.registerType)
|
|
|
? byteLength
|
|
|
- : alignEvenByteLength(byteLength)
|
|
|
+ : (byteAddressed ? byteLength : alignEvenByteLength(byteLength))
|
|
|
const wordQuantity = isBitRegisterType(baseGroup.registerType)
|
|
|
? Math.max(1, nextAddress - startAddress)
|
|
|
- : Math.max(1, paddedByteLength / 2)
|
|
|
- const addressOverflow = isAddressRangeOverflow(startAddress, wordQuantity)
|
|
|
- const endAddress = startAddress + wordQuantity - 1
|
|
|
+ : (byteAddressed ? Math.max(1, byteLength) : Math.max(1, paddedByteLength / 2))
|
|
|
+ const addressSpan = byteAddressed ? Math.max(1, byteLength) : wordQuantity
|
|
|
+ const addressOverflow = isAddressRangeOverflow(startAddress, addressSpan)
|
|
|
+ const endAddress = startAddress + addressSpan - 1
|
|
|
|
|
|
return {
|
|
|
...baseGroup,
|
|
|
- addressRangeText: formatAddressRange(startAddress, wordQuantity),
|
|
|
+ addressRangeText: formatAddressRange(startAddress, addressSpan),
|
|
|
addressOverflow,
|
|
|
addressWarningText: addressOverflow ? '地址超出 0xFFFF' : '',
|
|
|
endAddressText: addressOverflow ? `0x${padHex(MAX_MODBUS_ADDRESS)}+` : `0x${padHex(endAddress)}`,
|
|
|
@@ -852,6 +1098,7 @@ function normalizeGroup(group) {
|
|
|
registerTypeText: registerType.label,
|
|
|
registers,
|
|
|
paddedByteLength,
|
|
|
+ sourceMetaText: createGroupSourceMetaText(baseGroup),
|
|
|
startAddressText: `0x${padHex(startAddress)}`,
|
|
|
wordQuantity,
|
|
|
writable: registerType.writable
|
|
|
@@ -912,9 +1159,12 @@ function cloneImportedGroup(group) {
|
|
|
textByteLength: register.textByteLength,
|
|
|
remark: register.remark,
|
|
|
unit: register.unit,
|
|
|
- value: register.value
|
|
|
+ value: register.value,
|
|
|
+ ...pickFields(register, STRUCT_REGISTER_FIELDS),
|
|
|
+ ...pickFields(register, SOURCE_REGISTER_FIELDS)
|
|
|
})),
|
|
|
- startAddress: group.startAddress
|
|
|
+ startAddress: group.startAddress,
|
|
|
+ ...pickFields(group, SOURCE_GROUP_FIELDS)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -956,6 +1206,36 @@ function getRegisterWordsFromWordCache(register, wordCache) {
|
|
|
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
|
|
|
@@ -963,6 +1243,13 @@ function decodeRegisterFromWordCache(register, wordCache) {
|
|
|
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,
|
|
|
@@ -970,6 +1257,13 @@ function getRegisterEncodedWords(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)))
|
|
|
@@ -978,18 +1272,41 @@ function getRegisterByteStart(register, groupStartAddress = 0) {
|
|
|
return Math.max(0, ((Number(register.address) || 0) - (Number(groupStartAddress) || 0)) * 2 + (Number(register.byteOffset) || 0))
|
|
|
}
|
|
|
|
|
|
-function getGroupEncodedWords(group) {
|
|
|
+function getGroupEncodedBytes(group, baseBytes = null) {
|
|
|
const byteLength = Math.max(2, Number(group && group.paddedByteLength) || ((Number(group && group.wordQuantity) || 1) * 2))
|
|
|
- const bytes = Array.from({ length: byteLength }, () => 0)
|
|
|
+ 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 || '寄存器'} 没有有效写入值`)
|
|
|
}
|
|
|
|
|
|
- const byteStart = getRegisterByteStart(register, group.startAddress)
|
|
|
for (let offset = 0; offset < register.byteLength; offset += 1) {
|
|
|
if (byteStart + offset < bytes.length) {
|
|
|
bytes[byteStart + offset] = Number(registerBytes[offset] || 0) & 0xFF
|
|
|
@@ -997,7 +1314,11 @@ function getGroupEncodedWords(group) {
|
|
|
}
|
|
|
})
|
|
|
|
|
|
- return bytesToWords(bytes)
|
|
|
+ return bytes
|
|
|
+}
|
|
|
+
|
|
|
+function getGroupEncodedWords(group, baseBytes = null) {
|
|
|
+ return bytesToWords(getGroupEncodedBytes(group, baseBytes))
|
|
|
}
|
|
|
|
|
|
function validateRegisterValue(register, value) {
|
|
|
@@ -1013,6 +1334,13 @@ function validateRegisterValue(register, value) {
|
|
|
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))
|
|
|
@@ -1040,15 +1368,20 @@ module.exports = {
|
|
|
MAX_MODBUS_ADDRESS,
|
|
|
REGISTER_TYPE_OPTIONS,
|
|
|
cloneImportedGroup,
|
|
|
+ decodeRegisterFromByteCache,
|
|
|
decodeRegisterFromWordCache,
|
|
|
decodeRegisterValue,
|
|
|
formatCoilDisplayValue,
|
|
|
formatRegisterValue,
|
|
|
getDataType,
|
|
|
+ getRegisterEncodedBytes,
|
|
|
getRegisterEncodedWords,
|
|
|
+ getGroupEncodedBytes,
|
|
|
getGroupEncodedWords,
|
|
|
getRegisterWordCount,
|
|
|
getRegisterJsonValue,
|
|
|
+ getRegisterBytesFromByteCache,
|
|
|
+ getRegisterWordsFromByteCache,
|
|
|
getRegisterWordsFromWordCache,
|
|
|
getRegisterWriteValueText,
|
|
|
isAddressRangeOverflow,
|