| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091 |
- const {
- clampInteger,
- createId,
- formatFixedValue,
- normalizeTextValue,
- padHex,
- pickFields
- } = require('../../utils/base-utils.js')
- const {
- BYTE_ADDRESS_MEMORY_AREAS,
- DATA_TYPE_OPTIONS,
- DEFAULT_DATA_TYPE,
- DEFAULT_REGISTER_TYPE,
- DEFAULT_TEXT_BYTE_LENGTH,
- GROUP_LAYOUT_REGISTER,
- GROUP_LAYOUT_STRUCT,
- MAX_MODBUS_ADDRESS,
- MAX_PARAMETER_GROUP_ITEMS,
- MAX_STORAGE_ADDRESS,
- REGISTER_TYPE_OPTIONS,
- SOURCE_GROUP_FIELDS,
- SOURCE_REGISTER_FIELDS,
- STRUCT_REGISTER_FIELDS
- } = require('./constants.js')
- const {
- evaluateValueFormula
- } = require('./value-formula.js')
- const {
- alignEvenByteLength,
- decodeRegisterValue,
- formatCoilDisplayValue,
- formatRawByteText,
- formatRawByteTextWithDefault,
- formatRawWordText,
- formatRegisterValue,
- getDataType,
- getDataTypeIndex,
- getRegisterByteLength,
- getRegisterValueTypeLabel,
- getRegisterWordCount,
- getRegisterWordCountAtOffset,
- isBitFieldRegister,
- isBitRegisterType,
- isByteRegister,
- isTextRegister,
- normalizeEnumOptions,
- normalizeBitOffset,
- normalizeBitWidth,
- normalizeTextByteLength,
- parseCoilValue,
- parseEnumValueText,
- parseNumberText,
- registerTypeIsBit,
- supportsRange,
- supportsUnit
- } = require('./value-codec.js')
- const {
- decodeRegisterFromByteCache,
- decodeRegisterFromWordCache,
- getGroupEncodedBytes,
- getGroupEncodedWords,
- getRegisterBytesFromByteCache,
- getRegisterEncodedBytes,
- getRegisterEncodedWords,
- getRegisterWordsFromByteCache,
- getRegisterWordsFromWordCache,
- getRegisterWriteValueText,
- splitWordSpans,
- validateRegisterValue
- } = require('./register-io.js')
- function normalizeAddress(value, fallback = 0) {
- if (typeof value === 'number') {
- return Number.isFinite(value) ? clampInteger(value, 0, MAX_MODBUS_ADDRESS, fallback) : 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) ? clampInteger(parsedHex, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
- }
- const numberValue = Number(text)
- return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
- }
- function normalizeStorageAddress(value, fallback = 0) {
- if (typeof value === 'number') {
- return Number.isFinite(value) ? clampInteger(value, 0, MAX_STORAGE_ADDRESS, fallback) : 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) ? clampInteger(parsedHex, 0, MAX_STORAGE_ADDRESS, fallback) : fallback
- }
- const numberValue = Number(text)
- return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_STORAGE_ADDRESS, fallback) : fallback
- }
- function parseConfigAddress(value, options = {}) {
- const maxAddress = Number.isFinite(Number(options.maxAddress)) ? Number(options.maxAddress) : MAX_MODBUS_ADDRESS
- const maxHexLength = maxAddress > MAX_MODBUS_ADDRESS ? 8 : 4
- const label = options.label || '寄存器起始地址'
- if (typeof value === 'number') {
- return clampInteger(value, 0, maxAddress, 0)
- }
- const text = String(value === undefined || value === null ? '' : value).trim()
- const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
- if (!new RegExp(`^[0-9A-F]{1,${maxHexLength}}$`, 'i').test(hexText)) {
- throw new Error(`${label}无效`)
- }
- return parseInt(hexText, 16)
- }
- function parseConfigQuantity(value, maxQuantity) {
- const text = String(value === undefined || value === null ? '' : value).trim()
- const quantity = Number(text)
- if (!Number.isInteger(quantity) || quantity < 1 || quantity > maxQuantity) {
- throw new Error(`寄存器数量需为 1 - ${maxQuantity}`)
- }
- return quantity
- }
- function getRegisterType(typeKey) {
- return REGISTER_TYPE_OPTIONS.find((item) => item.key === typeKey) || REGISTER_TYPE_OPTIONS[0]
- }
- function getRegisterTypeIndex(typeKey) {
- return Math.max(0, REGISTER_TYPE_OPTIONS.findIndex((item) => item.key === getRegisterType(typeKey).key))
- }
- function isStructLayout(layout) {
- return layout === GROUP_LAYOUT_STRUCT
- }
- function padWordHex(value) {
- const numberValue = Number(value || 0)
- const length = numberValue > MAX_MODBUS_ADDRESS ? 8 : 4
- return numberValue.toString(16).toUpperCase().padStart(length, '0')
- }
- function normalizeStorageAddressWidth(value, address = 0) {
- const numberValue = Number(value)
- if (numberValue === 16 || numberValue === 2) return 16
- if (numberValue === 32 || numberValue === 4) return 32
- return Number(address) > MAX_MODBUS_ADDRESS ? 32 : 16
- }
- function resolveStorageAddressWidth(source = {}, address = 0) {
- const codeInfoContext = source.codeInfoContext || {}
- const explicitWidth = source.sourceAddressWidth
- || source.storageAddressWidth
- || source.addressWidth
- || codeInfoContext.storageAddressWidth
- || codeInfoContext.addressWidth
- || codeInfoContext.codeInfoAddressWidth
- const explicitByteLength = source.sourceAddressByteLength
- || source.addressByteLength
- || codeInfoContext.sourceAddressByteLength
- || codeInfoContext.addressByteLength
- if (explicitWidth) return normalizeStorageAddressWidth(explicitWidth, address)
- if (explicitByteLength) return normalizeStorageAddressWidth(explicitByteLength, address)
- const memoryArea = String(source.sourceMemoryArea || '').trim().toUpperCase()
- if (memoryArea === 'ADDR32' || memoryArea === 'ADDRESS32') return 32
- return normalizeStorageAddressWidth(0, address)
- }
- function getStorageHexWidth(addressWidth, address = 0) {
- return normalizeStorageAddressWidth(addressWidth, address) === 16 ? 4 : 8
- }
- function getStorageAddressMax(addressWidth, address = 0) {
- return normalizeStorageAddressWidth(addressWidth, address) === 16
- ? MAX_MODBUS_ADDRESS
- : MAX_STORAGE_ADDRESS
- }
- function padStorageHex(value, addressWidth = 0) {
- const numberValue = Math.max(0, Math.floor(Number(value) || 0))
- return numberValue.toString(16).toUpperCase().padStart(getStorageHexWidth(addressWidth, numberValue), '0')
- }
- function formatAddressRange(startAddress, wordCount) {
- const address = normalizeAddress(startAddress, 0)
- const count = Math.max(1, Number(wordCount) || 1)
- const endAddress = address + count - 1
- const safeEndAddress = Math.min(endAddress, MAX_MODBUS_ADDRESS)
- const overflowText = endAddress > MAX_MODBUS_ADDRESS ? '+' : ''
- if (count <= 1) return `0x${padWordHex(address)}`
- return `0x${padWordHex(address)}-0x${padWordHex(safeEndAddress)}${overflowText}`
- }
- function formatStorageAddressRange(startAddress, byteCount, addressWidth = 0) {
- const address = normalizeStorageAddress(startAddress, 0)
- const count = Math.max(1, Number(byteCount) || 1)
- const addressMax = getStorageAddressMax(addressWidth, address)
- const endAddress = address + count - 1
- const safeEndAddress = Math.min(endAddress, addressMax)
- const overflowText = endAddress > addressMax ? '+' : ''
- if (count <= 1) return `0x${padStorageHex(address, addressWidth)}${overflowText}`
- return `0x${padStorageHex(address, addressWidth)}-0x${padStorageHex(safeEndAddress, addressWidth)}${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'}`
- 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)
- return address + count - 1 > MAX_MODBUS_ADDRESS
- }
- function isStorageAddressRangeOverflow(startAddress, byteCount, addressWidth = 0) {
- const address = normalizeStorageAddress(startAddress, 0)
- const count = Math.max(1, Number(byteCount) || 1)
- const addressMax = getStorageAddressMax(addressWidth, address)
- return address + count - 1 > addressMax
- }
- function getMaxQuantity() {
- return MAX_PARAMETER_GROUP_ITEMS
- }
- function getRegisterSavedValue(register) {
- if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
- if (register.value !== undefined && register.value !== null) return normalizeTextValue(register.value)
- return null
- }
- function normalizeRegisterDataType(register, registerType) {
- if (isBitRegisterType(registerType)) return DEFAULT_DATA_TYPE
- return getDataType(register.dataType || register.type || DEFAULT_DATA_TYPE).key
- }
- function createRegisterSourceMetaText(register) {
- const bitText = isBitFieldRegister(register)
- ? `bit${normalizeBitOffset(register.bitOffset)}:${normalizeBitWidth(register.bitWidth)}`
- : ''
- const sourceByteLength = Number(register.sourceByteLength)
- const sourceByteLengthText = Number.isFinite(sourceByteLength) && sourceByteLength > 0
- ? `${Math.floor(sourceByteLength)}B`
- : ''
- const parts = [
- register.sourceMemoryArea,
- register.sourceAddressText,
- sourceByteLengthText,
- 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 formatCompactNumber(value, precision = 4) {
- const text = formatFixedValue(value, precision)
- return text === '--' ? '--' : text.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
- }
- function formatCardMetricValue(value) {
- if (value === '' || value === null || value === undefined) return ''
- return formatCompactNumber(value)
- }
- function readOptionalNumber(value) {
- if (value === '' || value === null || value === undefined) return null
- const numberValue = Number(value)
- return Number.isFinite(numberValue) ? numberValue : null
- }
- function addMetricItem(items, value, unit = '') {
- const text = formatCardMetricValue(value)
- if (!text) return
- items.push({
- text: `${text}${unit}`
- })
- }
- function assignOptionalNumber(target, key, value) {
- if (value === null || value === undefined) return
- target[key] = value
- }
- function normalizeMemoryEndian(value, fallback = 'big') {
- const text = String(value || '').trim().toLowerCase()
- if (text === 'little' || text === 'le' || text === '1') return 'little'
- if (text === 'big' || text === 'be' || text === '0') return 'big'
- return fallback
- }
- function normalizeStorageCodeInfoCard(codeInfo = null) {
- const hasCodeInfo = !!codeInfo && codeInfo.hasCodeInfo !== false
- const refVolt = hasCodeInfo ? readOptionalNumber(codeInfo.refVolt) : null
- const refVoltRaw = hasCodeInfo ? readOptionalNumber(codeInfo.refVoltRaw) : null
- const card = {
- alongDiv: hasCodeInfo ? readOptionalNumber(codeInfo.alongDiv) : null,
- ampGain: hasCodeInfo ? readOptionalNumber(codeInfo.ampGain) : null,
- busDiv: hasCodeInfo ? readOptionalNumber(codeInfo.busDiv) : null,
- caveFreq: hasCodeInfo ? readOptionalNumber(codeInfo.caveFreq) : null,
- chipModel: hasCodeInfo ? String(codeInfo.chipModel || '').trim() : '',
- maxPacketLength: hasCodeInfo ? (Number(codeInfo.maxPacketLength) || 0) : 0,
- memoryEndian: hasCodeInfo ? normalizeMemoryEndian(codeInfo.memoryEndian) : 'big',
- memoryEndianRaw: hasCodeInfo ? (Number(codeInfo.memoryEndianRaw) || 0) : 0,
- model: hasCodeInfo ? String(codeInfo.model || '').trim() : '',
- refVolt: hasCodeInfo
- ? (refVolt !== null ? refVolt : (refVoltRaw !== null ? refVoltRaw / 10 : null))
- : null,
- refVoltRaw: refVoltRaw === null ? 0 : refVoltRaw,
- rsShunt: hasCodeInfo ? readOptionalNumber(codeInfo.rsShunt) : null,
- addressWidth: hasCodeInfo
- ? (Number(codeInfo.addressWidth || codeInfo.codeInfoAddressWidth || codeInfo.storageAddressWidth) || 0)
- : 0
- }
- const codeInfoContext = hasCodeInfo
- ? {
- addressWidth: card.addressWidth,
- codeInfoAddressWidth: card.addressWidth,
- maxPacketLength: card.maxPacketLength,
- memoryEndian: card.memoryEndian,
- memoryEndianRaw: card.memoryEndianRaw,
- storageAddressWidth: card.addressWidth
- }
- : {}
- if (hasCodeInfo) {
- assignOptionalNumber(codeInfoContext, 'alongDiv', card.alongDiv)
- assignOptionalNumber(codeInfoContext, 'ampGain', card.ampGain)
- assignOptionalNumber(codeInfoContext, 'busDiv', card.busDiv)
- assignOptionalNumber(codeInfoContext, 'caveFreq', card.caveFreq)
- assignOptionalNumber(codeInfoContext, 'refVolt', card.refVolt)
- assignOptionalNumber(codeInfoContext, 'rsShunt', card.rsShunt)
- }
- const metricItems = []
- addMetricItem(metricItems, card.caveFreq, 'KHz')
- addMetricItem(metricItems, card.refVolt, 'V')
- addMetricItem(metricItems, card.ampGain)
- addMetricItem(metricItems, card.rsShunt, 'mΩ')
- return {
- ...card,
- codeInfoContext,
- hasCodeInfo,
- metricItems
- }
- }
- function isStorageStructGroup(group = {}) {
- return isStructLayout(group.layout)
- && isByteAddressedGroup(group)
- && !!String(group.sourceMemoryArea || '').trim()
- }
- function isStorageMemoryGroup(group = {}) {
- return isByteAddressedGroup(group)
- && !!String(group.sourceMemoryArea || '').trim()
- }
- function isStorageScalarEntryKind(entryKind) {
- const text = String(entryKind || '').trim().toLowerCase()
- return text === 'enum' || text === 'variable'
- }
- function isStorageScalarGroup(group = {}) {
- return isStorageMemoryGroup(group)
- && !isStructLayout(group.layout)
- && isStorageScalarEntryKind(group.sourceEntryKind)
- }
- function isStorageSourceLockedGroup(group = {}) {
- return isStorageMemoryGroup(group)
- }
- function isStorageSourceLockedRegister(group = {}, register = {}) {
- return isStorageSourceLockedGroup(group) || isStorageMemoryGroup(register)
- }
- function isParameterGroupPollEnabled(group = {}) {
- return group.pollEnabled !== false
- }
- function getStorageAreaText(group = {}, lowercase = false) {
- const area = String(group.sourceMemoryArea || '').trim()
- return lowercase ? area.toLowerCase() : area.toUpperCase()
- }
- function stripStorageAreaPrefix(text) {
- const source = String(text || '').trim()
- const cleaned = source.replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '').trim()
- return cleaned || source
- }
- function formatRegisterStartAddressText(address) {
- return `0x${padWordHex(address)}`
- }
- function formatStorageStartAddressText(address, addressWidth = 0) {
- return `0x${padStorageHex(address, addressWidth)}`
- }
- function formatStartAddressText(address, maxAddress = MAX_MODBUS_ADDRESS) {
- return `0x${padHex(address, maxAddress > MAX_MODBUS_ADDRESS ? 8 : 4)}`
- }
- function formatStorageHexBytes(bytes = [], byteLength = 1) {
- const safeLength = Math.max(1, Math.floor(Number(byteLength) || 1))
- const source = Array.isArray(bytes) ? bytes : []
- return `0x${Array.from({ length: safeLength }, (_, index) => (
- (Number(source[index] || 0) & 0xFF).toString(16).toUpperCase().padStart(2, '0')
- )).join('')}`
- }
- function formatStorageRegisterHexValue(register, rawBytes = []) {
- if (isTextRegister(register.dataType)) {
- return formatRawByteTextWithDefault(rawBytes, register.byteLength)
- }
- return formatStorageHexBytes(rawBytes, register.byteLength)
- }
- function getStorageRegisterTypeText(register = {}) {
- const enumType = String(register.enumName || '').trim()
- if (enumType) return enumType
- const entryKind = String(register.sourceEntryKind || '').trim().toLowerCase()
- const sourceValueType = String(register.sourceValueType || register.sourceElementType || '').trim().toLowerCase()
- const isAggregateField = entryKind === 'struct' || sourceValueType === 'struct'
- const valueTypeCandidates = [
- register.dataTypeText,
- register.dataType
- ]
- const sourceTypeCandidates = [
- register.sourceDefinitionName,
- register.sourceSymbolType,
- register.sourceValueType,
- register.sourceElementType
- ]
- const candidates = isAggregateField
- ? valueTypeCandidates.concat(sourceTypeCandidates)
- : sourceTypeCandidates.concat(valueTypeCandidates)
- for (const value of candidates) {
- const text = String(value || '').trim()
- if (text && text !== '---') return text
- }
- return ''
- }
- function findEnumOptionByValue(register = {}) {
- const rawValue = Number(register.rawValue)
- if (!Number.isFinite(rawValue)) return null
- return normalizeEnumOptions(register).find((option) => (
- Number(option && option.value) === rawValue
- )) || null
- }
- function findEnumOptionByInputValue(register = {}) {
- const valueText = normalizeTextValue(register.inputValue).trim()
- if (!valueText || valueText === '--') return null
- let parsedValue = parseEnumValueText(register, valueText)
- if (parsedValue === null) parsedValue = parseNumberText(valueText, register.dataType)
- if (parsedValue === null) return null
- return normalizeEnumOptions(register).find((option) => (
- Number(option.value) === Number(parsedValue)
- )) || null
- }
- function findScalarEnumOption(register = {}) {
- if (register.isDirty) return findEnumOptionByInputValue(register)
- if (!hasReadScalarValue(register)) return null
- return findEnumOptionByValue(register)
- }
- function hasReadScalarValue(register = {}) {
- return register.rawValue !== null
- && register.rawValue !== undefined
- && !Array.isArray(register.rawValue)
- }
- function isEnumSourceText(value) {
- const text = String(value || '').trim().toLowerCase()
- return text === 'enum' || /^enum\b/.test(text) || /\benum\b/.test(text)
- }
- function isEnumLikeRegister(group = {}, register = {}) {
- if (String(register.enumName || '').trim()) return true
- if (normalizeEnumOptions(register).length) return true
- return [
- group.sourceEntryKind,
- group.sourceElementType,
- group.sourceValueType,
- group.sourceSymbolType,
- register.sourceEntryKind,
- register.sourceElementType,
- register.sourceValueType,
- register.sourceSymbolType
- ].some(isEnumSourceText)
- }
- function formatScalarDecimalValue(register = {}) {
- const rawValue = register.rawValue
- if (rawValue === null || rawValue === undefined || Array.isArray(rawValue)) return ''
- const enumOption = findEnumOptionByValue(register)
- if (enumOption) return `${enumOption.label || enumOption.name} (${Number(rawValue)})`
- const dataType = getDataType(register.dataType).key
- if (/^(?:u?int)(?:64|128|256)_t$/.test(dataType)) {
- return normalizeTextValue(rawValue || register.displayValue || '--') || '--'
- }
- const numberValue = Number(rawValue)
- if (!Number.isFinite(numberValue)) return normalizeTextValue(register.displayValue || '--') || '--'
- return (dataType === 'float' || dataType === 'double')
- ? formatCompactNumber(numberValue, 6)
- : String(Math.trunc(numberValue))
- }
- function formatScalarInputValue(register = {}) {
- const dirtyInputValue = normalizeTextValue(register.inputValue).trim()
- if (register.isDirty) return dirtyInputValue
- if (!hasReadScalarValue(register)) return ''
- const rawValue = register.rawValue
- if (typeof rawValue === 'bigint') return rawValue.toString()
- if (typeof rawValue === 'number') {
- if (!Number.isFinite(rawValue)) return ''
- return Number.isInteger(rawValue) ? String(rawValue) : formatCompactNumber(rawValue, 6)
- }
- return normalizeTextValue(rawValue).trim()
- }
- function createScalarDefinitionText(register = {}, group = {}) {
- const enumOption = findEnumOptionByValue(register)
- const typeName = String(register.enumName || register.sourceSymbolType || group.sourceSymbolType || '').trim()
- const dataTypeText = register.dataType === 'raw' ? '' : (register.dataTypeText || register.dataType)
- const definitionText = typeName || dataTypeText
- if (enumOption && definitionText) return `${definitionText}:${enumOption.label || enumOption.name}`
- if (enumOption) return enumOption.label || enumOption.name
- return definitionText
- }
- function createScalarEnumDefinitionText(register = {}, group = {}) {
- if (!normalizeEnumOptions(register).length) return ''
- const enumOption = findScalarEnumOption(register)
- if (!enumOption) return ''
- return enumOption.label || enumOption.name
- }
- 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 isStorageMemory = isStorageMemoryGroup(group)
- const sourceMetadataLocked = isStorageSourceLockedRegister(group, 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 isPlaceholderByteField = !!register.isPlaceholderByteField
- const textByteLength = isTextRegister(dataType)
- ? normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
- : ''
- const defaultValue = normalizeTextValue(register.defaultValue)
- const savedValue = getRegisterSavedValue(register)
- const inputValue = savedValue === null ? defaultValue : savedValue
- const rawValue = register.rawValue === undefined ? null : register.rawValue
- const memoryEndian = isStorageMemory
- ? normalizeMemoryEndian(register.memoryEndian || group.codeInfoContext && group.codeInfoContext.memoryEndian)
- : 'big'
- const storageAddressWidth = isStorageMemory
- ? resolveStorageAddressWidth({
- ...group,
- sourceAddressByteLength: register.sourceAddressByteLength || group.sourceAddressByteLength,
- sourceAddressWidth: register.sourceAddressWidth || group.sourceAddressWidth,
- sourceMemoryArea: register.sourceMemoryArea || group.sourceMemoryArea
- }, address)
- : 0
- 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 defaultRawValueText = rawValue === null
- ? '--'
- : (isBitRegisterType(registerType)
- ? formatCoilDisplayValue(rawValue)
- : (byteAddressed ? formatRawByteText(rawBytes) : formatRawWordText(rawWords)))
- const hasRawValue = rawValue !== null && rawValue !== undefined
- const rawValueText = isStorageMemory
- ? (hasRawValue ? formatStorageRegisterHexValue({
- ...register,
- byteLength,
- dataType
- }, rawBytes) : '')
- : defaultRawValueText
- const displayValue = rawValue === null
- ? (inputValue.trim() ? inputValue : '--')
- : formatRegisterValue({ ...register, dataType, byteOffset, memoryEndian }, rawValue)
- const conversionFormula = normalizeTextValue(register.conversionFormula).trim()
- const conversionResult = conversionFormula && rawValue !== null
- ? evaluateValueFormula(conversionFormula, rawValue, group.codeInfoContext || {})
- : null
- const convertedDisplayValue = conversionResult && conversionResult.ok
- ? conversionResult.text
- : displayValue
- const displayMetaText = conversionResult && conversionResult.ok
- ? `raw ${displayValue}`
- : ''
- const addressRangeText = isBitRegisterType(registerType)
- ? `0x${padHex(address)}`
- : (byteAddressed
- ? (isStorageMemory
- ? formatStorageAddressRange(address, Math.max(1, byteLength), storageAddressWidth)
- : formatAddressRange(address, Math.max(1, byteLength)))
- : formatAddressRange(address, registerCount))
- const addressText = isBitField
- ? (byteAddressed
- ? `${isStorageMemory
- ? formatStorageAddressRange(address, Math.max(1, byteLength), storageAddressWidth)
- : formatAddressRange(address, Math.max(1, byteLength))}.b${bitOffset}${bitWidth > 1 ? `..b${bitOffset + bitWidth - 1}` : ''}`
- : formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth))
- : (byteAddressed
- ? (isStorageMemory
- ? formatStorageAddressRange(address, Math.max(1, byteLength), storageAddressWidth)
- : formatAddressRange(address, Math.max(1, byteLength)))
- : formatRegisterAddressText(address, byteOffset, byteLength, registerType))
- const registerStartAddressText = isStorageMemory
- ? formatStorageStartAddressText(address, storageAddressWidth)
- : formatRegisterStartAddressText(address)
- const registerTypeText = getStorageRegisterTypeText({
- ...register,
- dataType,
- dataTypeText: getRegisterValueTypeLabel(dataType)
- })
- const metaText = isStorageMemory
- ? [registerStartAddressText, registerTypeText, rawValueText].filter(Boolean).join(' ')
- : `${addressText} ${rawValueText}`.trim()
- const cardMetaText = isStorageMemory
- ? metaText
- : (sourceMetadataLocked ? '' : metaText)
- const name = register.name || `寄存器 ${index + 1}`
- return {
- address,
- addressRangeText,
- addressText,
- bitOffset: isBitField ? bitOffset : '',
- bitWidth: isBitField ? bitWidth : '',
- byteLength,
- byteLengthText: isBitRegisterType(registerType)
- ? '1bit'
- : (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),
- dataTypeText: getRegisterValueTypeLabel(dataType),
- defaultValue,
- displayMetaText,
- displayValue: convertedDisplayValue,
- id: register.id || createId('gm-reg'),
- inputType: isTextRegister(dataType) ? 'text' : 'text',
- inputValue,
- isStructField: layout === GROUP_LAYOUT_STRUCT || !!register.isStructField,
- layout,
- isBitField,
- isPlaceholderByteField,
- isDirty: !!register.isDirty,
- maxValue: normalizeTextValue(register.maxValue),
- displayName: name,
- metaText: cardMetaText,
- minValue: normalizeTextValue(register.minValue),
- memoryEndian,
- name,
- conversionFormula,
- conversionFormulaErrorText: conversionResult && conversionResult.errorText ? conversionResult.errorText : '',
- rawValue,
- rawValueText,
- rawBytes,
- rawWords,
- registerCount,
- registerStartAddressText,
- registerTypeText,
- byteOffset,
- registerType,
- showDataType: !isBitRegisterType(registerType),
- showRange: !isBitRegisterType(registerType) && !isBitField && supportsRange(dataType),
- showTextLength: !isBitRegisterType(registerType) && isTextRegister(dataType),
- showUnit: canShowUnit,
- textByteLength,
- unit: canShowUnit ? normalizeTextValue(register.unit).trim() : '',
- structByteLength: register.structByteLength,
- remark: register.remark || '',
- ...pickFields(register, SOURCE_REGISTER_FIELDS),
- sourceMetaText: createRegisterSourceMetaText(register),
- sourceMetadataLocked
- }
- }
- function normalizeGroup(group) {
- const registerType = getRegisterType(group.registerType || group.type || DEFAULT_REGISTER_TYPE)
- const sourceMemoryArea = String(group.sourceMemoryArea || '').trim()
- const isStorageGroupSource = !!sourceMemoryArea || group.addressUnit === 'byte' || group.addressUnit === 'bytes'
- const startAddress = isStorageGroupSource
- ? normalizeStorageAddress(
- group.sourceAddress !== undefined && group.sourceAddress !== null && group.sourceAddress !== ''
- ? group.sourceAddress
- : group.startAddress,
- 0
- )
- : normalizeAddress(group.startAddress, 0)
- const maxQuantity = getMaxQuantity(registerType.key)
- const sourceRegisters = Array.isArray(group.registers) ? group.registers : []
- const codeInfoContext = group.codeInfoContext || {}
- 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)
- : clampInteger(sourceRegisters.length || 1, 1, maxQuantity, 1)
- const baseGroup = {
- deleteVisible: !!group.deleteVisible,
- expanded: group.expanded === true,
- id: group.id || createId('gm-group'),
- isStructLayout: layout === GROUP_LAYOUT_STRUCT,
- layout,
- name: String(group.name || group.groupName || '寄存器组').trim() || '寄存器组',
- pollEnabled: group.pollEnabled === false ? false : true,
- quantity,
- registerType: registerType.key,
- startAddress,
- touchStartX: 0,
- codeInfoContext,
- ...pickFields(group, SOURCE_GROUP_FIELDS)
- }
- const registers = []
- let nextAddress = startAddress
- let nextByteOffset = 0
- for (let index = 0; index < quantity; index += 1) {
- const sourceRegister = sourceRegisters[index] || {}
- let normalizedSourceRegister = sourceRegister
- const dataType = normalizeRegisterDataType(sourceRegister, baseGroup.registerType)
- const textByteLength = isTextRegister(dataType)
- ? normalizeTextByteLength(sourceRegister.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
- : ''
- const isBitRegister = isBitRegisterType(baseGroup.registerType)
- let address = startAddress + index
- let byteOffset = 0
- if (!isBitRegister) {
- 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
- }
- 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: currentByteStart
- }
- nextByteOffset = Math.max(nextByteOffset, currentByteStart + byteLength)
- }
- const register = normalizeRegister(normalizedSourceRegister, baseGroup, index, address, byteOffset)
- registers.push(register)
- if (isBitRegister) nextAddress += register.registerCount
- }
- const byteLength = isBitRegisterType(baseGroup.registerType)
- ? Math.max(1, nextAddress - startAddress)
- : Math.max(1, nextByteOffset)
- const paddedByteLength = isBitRegisterType(baseGroup.registerType)
- ? byteLength
- : (byteAddressed ? byteLength : alignEvenByteLength(byteLength))
- const wordQuantity = isBitRegisterType(baseGroup.registerType)
- ? Math.max(1, nextAddress - startAddress)
- : (byteAddressed ? Math.max(1, byteLength) : Math.max(1, paddedByteLength / 2))
- const addressSpan = byteAddressed ? Math.max(1, byteLength) : wordQuantity
- const storageMemory = isStorageMemoryGroup(baseGroup)
- const storageScalarGroup = isStorageScalarGroup(baseGroup)
- const sourceMetadataLocked = isStorageSourceLockedGroup(baseGroup)
- const storageAddressWidth = storageMemory ? resolveStorageAddressWidth(baseGroup, startAddress) : 0
- const storageAddressMax = getStorageAddressMax(storageAddressWidth, startAddress)
- const addressOverflow = storageMemory
- ? isStorageAddressRangeOverflow(startAddress, addressSpan, storageAddressWidth)
- : isAddressRangeOverflow(startAddress, addressSpan)
- const endAddress = startAddress + addressSpan - 1
- const addressMax = storageMemory ? storageAddressMax : MAX_MODBUS_ADDRESS
- const startAddressText = storageMemory
- ? formatStorageStartAddressText(startAddress, storageAddressWidth)
- : formatStartAddressText(startAddress, addressMax)
- const endAddressText = storageMemory
- ? (addressOverflow ? `0x${padStorageHex(addressMax, storageAddressWidth)}+` : `0x${padStorageHex(endAddress, storageAddressWidth)}`)
- : (addressOverflow ? `0x${padWordHex(addressMax)}+` : `0x${padWordHex(endAddress)}`)
- const scalarRegister = storageScalarGroup ? (registers[0] || null) : null
- const scalarDefinitionText = scalarRegister ? createScalarDefinitionText(scalarRegister, baseGroup) : ''
- const scalarEnumDefinitionText = scalarRegister ? createScalarEnumDefinitionText(scalarRegister, baseGroup) : ''
- const scalarInputValueText = scalarRegister ? formatScalarInputValue(scalarRegister) : ''
- const scalarRawHexText = scalarRegister ? scalarRegister.rawValueText : ''
- const scalarValueText = scalarRegister ? formatScalarDecimalValue(scalarRegister) : ''
- const isStorageEnumScalar = !!(storageScalarGroup && scalarRegister && isEnumLikeRegister(baseGroup, scalarRegister))
- const displayName = storageMemory
- ? stripStorageAreaPrefix(baseGroup.name)
- : baseGroup.name
- const storageCardMetaParts = [startAddressText]
- if (storageScalarGroup && scalarEnumDefinitionText) {
- storageCardMetaParts.push(scalarEnumDefinitionText)
- }
- const storageCardMetaText = storageMemory
- ? storageCardMetaParts.filter(Boolean).join(' ')
- : ''
- const listMetaText = storageMemory
- ? (sourceMetadataLocked
- ? ''
- : (storageScalarGroup
- ? [
- getStorageAreaText(baseGroup),
- startAddressText,
- `${byteLength}B`,
- scalarRawHexText,
- scalarDefinitionText
- ].filter(Boolean).join(' ')
- : `${getStorageAreaText(baseGroup)} ${startAddressText}-${endAddressText} ${byteLength}B`))
- : `${formatAddressRange(startAddress, addressSpan)} · ${quantity}/${wordQuantity}${createGroupSourceMetaText(baseGroup) ? ` · ${createGroupSourceMetaText(baseGroup)}` : ''}${addressOverflow ? ' · 地址超出 0xFFFF' : ''}`
- const detailMetaText = storageMemory
- ? (sourceMetadataLocked ? '' : `${getStorageAreaText(baseGroup, true)} ${startAddressText} ${quantity}/${byteLength}B`)
- : `${formatAddressRange(startAddress, addressSpan)} · ${quantity}/${wordQuantity}${createGroupSourceMetaText(baseGroup) ? ` · ${createGroupSourceMetaText(baseGroup)}` : ''}${addressOverflow ? ' · 地址超出 0xFFFF' : ''}`
- const sourceMetaText = createGroupSourceMetaText(baseGroup)
- return {
- ...baseGroup,
- addressRangeText: storageMemory
- ? formatStorageAddressRange(startAddress, addressSpan, storageAddressWidth)
- : formatAddressRange(startAddress, addressSpan),
- addressOverflow,
- addressWarningText: addressOverflow
- ? `地址超出 0x${storageMemory ? padStorageHex(addressMax, storageAddressWidth) : padWordHex(addressMax)}`
- : '',
- detailMetaText,
- detailTitleText: displayName,
- displayName,
- endAddressText,
- functionCode: registerType.functionCode,
- isReadOnly: !registerType.writable,
- isStorageEnumScalar,
- isStorageScalarGroup: storageScalarGroup,
- byteLength,
- listMetaText,
- maxQuantity,
- registerTypeIndex: getRegisterTypeIndex(registerType.key),
- registerTypeText: registerType.label,
- registers,
- paddedByteLength,
- scalarDefinitionText,
- scalarEnumDefinitionText,
- scalarInputValueText,
- scalarRawHexText,
- scalarValueText,
- sourceMetaText,
- sourceMetadataLocked,
- startAddressText,
- storageCardMetaText,
- wordQuantity,
- writable: registerType.writable
- }
- }
- function normalizeGroupConfig(config = {}) {
- const registerType = config.registerTypeIndex !== undefined && config.registerTypeIndex !== null
- ? (REGISTER_TYPE_OPTIONS[Number(config.registerTypeIndex)] || REGISTER_TYPE_OPTIONS[0])
- : getRegisterType(config.registerType || config.type || DEFAULT_REGISTER_TYPE)
- const maxQuantity = getMaxQuantity(registerType.key)
- const isStorageConfig = !!String(config.sourceMemoryArea || '').trim()
- || config.addressUnit === 'byte'
- || config.addressUnit === 'bytes'
- const storageAddressWidth = isStorageConfig ? resolveStorageAddressWidth(config, 0) : 0
- const storageAddressByteLength = storageAddressWidth === 16 ? 2 : 4
- return {
- ...(isStorageConfig ? {
- addressUnit: config.addressUnit || 'byte',
- sourceAddressByteLength: config.sourceAddressByteLength || storageAddressByteLength,
- sourceAddressWidth: storageAddressWidth,
- sourceMemoryArea: config.sourceMemoryArea || 'XDATA'
- } : {}),
- layout: isStructLayout(config.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
- name: String(config.name || config.groupName || '寄存器组').trim() || '寄存器组',
- pollEnabled: config.pollEnabled === false ? false : true,
- quantity: parseConfigQuantity(config.quantity, maxQuantity),
- registerType: registerType.key,
- startAddress: parseConfigAddress(config.startAddress, {
- label: isStorageConfig ? '内存起始地址' : '寄存器起始地址',
- maxAddress: isStorageConfig ? getStorageAddressMax(storageAddressWidth) : MAX_MODBUS_ADDRESS
- })
- }
- }
- function getRegisterJsonValue(register) {
- if (register.inputValue !== undefined && register.inputValue !== null) {
- return normalizeTextValue(register.inputValue)
- }
- if (register.defaultValue !== undefined && register.defaultValue !== null) {
- return normalizeTextValue(register.defaultValue)
- }
- if (register.displayValue !== undefined && register.displayValue !== null && register.displayValue !== '--') {
- return normalizeTextValue(register.displayValue)
- }
- return ''
- }
- function normalizeImportedRegisterDataType(register) {
- const dataType = register.dataType || register.type || DEFAULT_DATA_TYPE
- return getDataType(dataType).key
- }
- function cloneImportedGroup(group) {
- return {
- layout: isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
- name: group.name,
- pollEnabled: group.pollEnabled === false ? false : true,
- quantity: group.quantity,
- registerType: group.registerType || group.type || DEFAULT_REGISTER_TYPE,
- registers: (Array.isArray(group.registers) ? group.registers : []).map((register) => ({
- conversionFormula: register.conversionFormula,
- dataType: normalizeImportedRegisterDataType(register),
- defaultValue: register.defaultValue,
- inputValue: register.inputValue,
- maxValue: register.maxValue,
- minValue: register.minValue,
- name: register.name,
- isStructField: !!register.isStructField,
- textByteLength: register.textByteLength,
- remark: register.remark,
- unit: register.unit,
- value: register.value,
- ...pickFields(register, STRUCT_REGISTER_FIELDS),
- ...pickFields(register, SOURCE_REGISTER_FIELDS)
- })),
- startAddress: group.startAddress,
- ...pickFields(group, SOURCE_GROUP_FIELDS)
- }
- }
- module.exports = {
- DATA_TYPE_OPTIONS,
- DEFAULT_DATA_TYPE,
- DEFAULT_REGISTER_TYPE,
- GROUP_LAYOUT_REGISTER,
- GROUP_LAYOUT_STRUCT,
- MAX_MODBUS_ADDRESS,
- MAX_STORAGE_ADDRESS,
- REGISTER_TYPE_OPTIONS,
- cloneImportedGroup,
- decodeRegisterFromByteCache,
- decodeRegisterFromWordCache,
- decodeRegisterValue,
- formatCoilDisplayValue,
- formatRegisterValue,
- getDataType,
- getRegisterEncodedBytes,
- getRegisterEncodedWords,
- getGroupEncodedBytes,
- getGroupEncodedWords,
- getRegisterWordCount,
- getRegisterJsonValue,
- getRegisterBytesFromByteCache,
- getRegisterWordsFromByteCache,
- getRegisterWordsFromWordCache,
- getRegisterWriteValueText,
- isAddressRangeOverflow,
- isBitRegisterType,
- isByteRegister,
- isParameterGroupPollEnabled,
- isStorageMemoryGroup,
- isStorageScalarGroup,
- isStorageSourceLockedGroup,
- isStorageSourceLockedRegister,
- isStorageStructGroup,
- isTextRegister,
- normalizeGroup,
- normalizeGroupConfig,
- normalizeStorageCodeInfoCard,
- normalizeRegister,
- parseCoilValue,
- registerTypeIsBit,
- splitWordSpans,
- validateRegisterValue
- }
|