model.js 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091
  1. const {
  2. clampInteger,
  3. createId,
  4. formatFixedValue,
  5. normalizeTextValue,
  6. padHex,
  7. pickFields
  8. } = require('../../utils/base-utils.js')
  9. const {
  10. BYTE_ADDRESS_MEMORY_AREAS,
  11. DATA_TYPE_OPTIONS,
  12. DEFAULT_DATA_TYPE,
  13. DEFAULT_REGISTER_TYPE,
  14. DEFAULT_TEXT_BYTE_LENGTH,
  15. GROUP_LAYOUT_REGISTER,
  16. GROUP_LAYOUT_STRUCT,
  17. MAX_MODBUS_ADDRESS,
  18. MAX_PARAMETER_GROUP_ITEMS,
  19. MAX_STORAGE_ADDRESS,
  20. REGISTER_TYPE_OPTIONS,
  21. SOURCE_GROUP_FIELDS,
  22. SOURCE_REGISTER_FIELDS,
  23. STRUCT_REGISTER_FIELDS
  24. } = require('./constants.js')
  25. const {
  26. evaluateValueFormula
  27. } = require('./value-formula.js')
  28. const {
  29. alignEvenByteLength,
  30. decodeRegisterValue,
  31. formatCoilDisplayValue,
  32. formatRawByteText,
  33. formatRawByteTextWithDefault,
  34. formatRawWordText,
  35. formatRegisterValue,
  36. getDataType,
  37. getDataTypeIndex,
  38. getRegisterByteLength,
  39. getRegisterValueTypeLabel,
  40. getRegisterWordCount,
  41. getRegisterWordCountAtOffset,
  42. isBitFieldRegister,
  43. isBitRegisterType,
  44. isByteRegister,
  45. isTextRegister,
  46. normalizeEnumOptions,
  47. normalizeBitOffset,
  48. normalizeBitWidth,
  49. normalizeTextByteLength,
  50. parseCoilValue,
  51. parseEnumValueText,
  52. parseNumberText,
  53. registerTypeIsBit,
  54. supportsRange,
  55. supportsUnit
  56. } = require('./value-codec.js')
  57. const {
  58. decodeRegisterFromByteCache,
  59. decodeRegisterFromWordCache,
  60. getGroupEncodedBytes,
  61. getGroupEncodedWords,
  62. getRegisterBytesFromByteCache,
  63. getRegisterEncodedBytes,
  64. getRegisterEncodedWords,
  65. getRegisterWordsFromByteCache,
  66. getRegisterWordsFromWordCache,
  67. getRegisterWriteValueText,
  68. splitWordSpans,
  69. validateRegisterValue
  70. } = require('./register-io.js')
  71. function normalizeAddress(value, fallback = 0) {
  72. if (typeof value === 'number') {
  73. return Number.isFinite(value) ? clampInteger(value, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  74. }
  75. const text = String(value === undefined || value === null ? '' : value).trim()
  76. if (!text) return fallback
  77. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  78. if (/^[0-9A-F]+$/i.test(hexText)) {
  79. const parsedHex = parseInt(hexText, 16)
  80. return Number.isFinite(parsedHex) ? clampInteger(parsedHex, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  81. }
  82. const numberValue = Number(text)
  83. return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  84. }
  85. function normalizeStorageAddress(value, fallback = 0) {
  86. if (typeof value === 'number') {
  87. return Number.isFinite(value) ? clampInteger(value, 0, MAX_STORAGE_ADDRESS, fallback) : fallback
  88. }
  89. const text = String(value === undefined || value === null ? '' : value).trim()
  90. if (!text) return fallback
  91. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  92. if (/^[0-9A-F]+$/i.test(hexText)) {
  93. const parsedHex = parseInt(hexText, 16)
  94. return Number.isFinite(parsedHex) ? clampInteger(parsedHex, 0, MAX_STORAGE_ADDRESS, fallback) : fallback
  95. }
  96. const numberValue = Number(text)
  97. return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_STORAGE_ADDRESS, fallback) : fallback
  98. }
  99. function parseConfigAddress(value, options = {}) {
  100. const maxAddress = Number.isFinite(Number(options.maxAddress)) ? Number(options.maxAddress) : MAX_MODBUS_ADDRESS
  101. const maxHexLength = maxAddress > MAX_MODBUS_ADDRESS ? 8 : 4
  102. const label = options.label || '寄存器起始地址'
  103. if (typeof value === 'number') {
  104. return clampInteger(value, 0, maxAddress, 0)
  105. }
  106. const text = String(value === undefined || value === null ? '' : value).trim()
  107. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  108. if (!new RegExp(`^[0-9A-F]{1,${maxHexLength}}$`, 'i').test(hexText)) {
  109. throw new Error(`${label}无效`)
  110. }
  111. return parseInt(hexText, 16)
  112. }
  113. function parseConfigQuantity(value, maxQuantity) {
  114. const text = String(value === undefined || value === null ? '' : value).trim()
  115. const quantity = Number(text)
  116. if (!Number.isInteger(quantity) || quantity < 1 || quantity > maxQuantity) {
  117. throw new Error(`寄存器数量需为 1 - ${maxQuantity}`)
  118. }
  119. return quantity
  120. }
  121. function getRegisterType(typeKey) {
  122. return REGISTER_TYPE_OPTIONS.find((item) => item.key === typeKey) || REGISTER_TYPE_OPTIONS[0]
  123. }
  124. function getRegisterTypeIndex(typeKey) {
  125. return Math.max(0, REGISTER_TYPE_OPTIONS.findIndex((item) => item.key === getRegisterType(typeKey).key))
  126. }
  127. function isStructLayout(layout) {
  128. return layout === GROUP_LAYOUT_STRUCT
  129. }
  130. function padWordHex(value) {
  131. const numberValue = Number(value || 0)
  132. const length = numberValue > MAX_MODBUS_ADDRESS ? 8 : 4
  133. return numberValue.toString(16).toUpperCase().padStart(length, '0')
  134. }
  135. function normalizeStorageAddressWidth(value, address = 0) {
  136. const numberValue = Number(value)
  137. if (numberValue === 16 || numberValue === 2) return 16
  138. if (numberValue === 32 || numberValue === 4) return 32
  139. return Number(address) > MAX_MODBUS_ADDRESS ? 32 : 16
  140. }
  141. function resolveStorageAddressWidth(source = {}, address = 0) {
  142. const codeInfoContext = source.codeInfoContext || {}
  143. const explicitWidth = source.sourceAddressWidth
  144. || source.storageAddressWidth
  145. || source.addressWidth
  146. || codeInfoContext.storageAddressWidth
  147. || codeInfoContext.addressWidth
  148. || codeInfoContext.codeInfoAddressWidth
  149. const explicitByteLength = source.sourceAddressByteLength
  150. || source.addressByteLength
  151. || codeInfoContext.sourceAddressByteLength
  152. || codeInfoContext.addressByteLength
  153. if (explicitWidth) return normalizeStorageAddressWidth(explicitWidth, address)
  154. if (explicitByteLength) return normalizeStorageAddressWidth(explicitByteLength, address)
  155. const memoryArea = String(source.sourceMemoryArea || '').trim().toUpperCase()
  156. if (memoryArea === 'ADDR32' || memoryArea === 'ADDRESS32') return 32
  157. return normalizeStorageAddressWidth(0, address)
  158. }
  159. function getStorageHexWidth(addressWidth, address = 0) {
  160. return normalizeStorageAddressWidth(addressWidth, address) === 16 ? 4 : 8
  161. }
  162. function getStorageAddressMax(addressWidth, address = 0) {
  163. return normalizeStorageAddressWidth(addressWidth, address) === 16
  164. ? MAX_MODBUS_ADDRESS
  165. : MAX_STORAGE_ADDRESS
  166. }
  167. function padStorageHex(value, addressWidth = 0) {
  168. const numberValue = Math.max(0, Math.floor(Number(value) || 0))
  169. return numberValue.toString(16).toUpperCase().padStart(getStorageHexWidth(addressWidth, numberValue), '0')
  170. }
  171. function formatAddressRange(startAddress, wordCount) {
  172. const address = normalizeAddress(startAddress, 0)
  173. const count = Math.max(1, Number(wordCount) || 1)
  174. const endAddress = address + count - 1
  175. const safeEndAddress = Math.min(endAddress, MAX_MODBUS_ADDRESS)
  176. const overflowText = endAddress > MAX_MODBUS_ADDRESS ? '+' : ''
  177. if (count <= 1) return `0x${padWordHex(address)}`
  178. return `0x${padWordHex(address)}-0x${padWordHex(safeEndAddress)}${overflowText}`
  179. }
  180. function formatStorageAddressRange(startAddress, byteCount, addressWidth = 0) {
  181. const address = normalizeStorageAddress(startAddress, 0)
  182. const count = Math.max(1, Number(byteCount) || 1)
  183. const addressMax = getStorageAddressMax(addressWidth, address)
  184. const endAddress = address + count - 1
  185. const safeEndAddress = Math.min(endAddress, addressMax)
  186. const overflowText = endAddress > addressMax ? '+' : ''
  187. if (count <= 1) return `0x${padStorageHex(address, addressWidth)}${overflowText}`
  188. return `0x${padStorageHex(address, addressWidth)}-0x${padStorageHex(safeEndAddress, addressWidth)}${overflowText}`
  189. }
  190. function isByteAddressedGroup(group = {}) {
  191. const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()
  192. return group.addressUnit === 'byte'
  193. || group.addressUnit === 'bytes'
  194. || BYTE_ADDRESS_MEMORY_AREAS.indexOf(memoryArea) >= 0
  195. }
  196. function formatRegisterAddressText(address, byteOffset, byteLength, registerType) {
  197. if (isBitRegisterType(registerType)) return `0x${padHex(address)}`
  198. if (byteLength === 1) return `0x${padHex(address)}${byteOffset === 0 ? 'H' : 'L'}`
  199. return `0x${padHex(address)}`
  200. }
  201. function formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth) {
  202. const byteText = formatRegisterAddressText(address, byteOffset, 1, DEFAULT_REGISTER_TYPE)
  203. const startBit = normalizeBitOffset(bitOffset)
  204. const endBit = startBit + normalizeBitWidth(bitWidth) - 1
  205. return endBit === startBit
  206. ? `${byteText}.b${startBit}`
  207. : `${byteText}.b${startBit}..${endBit}`
  208. }
  209. function isAddressRangeOverflow(startAddress, wordCount) {
  210. const address = normalizeAddress(startAddress, 0)
  211. const count = Math.max(1, Number(wordCount) || 1)
  212. return address + count - 1 > MAX_MODBUS_ADDRESS
  213. }
  214. function isStorageAddressRangeOverflow(startAddress, byteCount, addressWidth = 0) {
  215. const address = normalizeStorageAddress(startAddress, 0)
  216. const count = Math.max(1, Number(byteCount) || 1)
  217. const addressMax = getStorageAddressMax(addressWidth, address)
  218. return address + count - 1 > addressMax
  219. }
  220. function getMaxQuantity() {
  221. return MAX_PARAMETER_GROUP_ITEMS
  222. }
  223. function getRegisterSavedValue(register) {
  224. if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
  225. if (register.value !== undefined && register.value !== null) return normalizeTextValue(register.value)
  226. return null
  227. }
  228. function normalizeRegisterDataType(register, registerType) {
  229. if (isBitRegisterType(registerType)) return DEFAULT_DATA_TYPE
  230. return getDataType(register.dataType || register.type || DEFAULT_DATA_TYPE).key
  231. }
  232. function createRegisterSourceMetaText(register) {
  233. const bitText = isBitFieldRegister(register)
  234. ? `bit${normalizeBitOffset(register.bitOffset)}:${normalizeBitWidth(register.bitWidth)}`
  235. : ''
  236. const sourceByteLength = Number(register.sourceByteLength)
  237. const sourceByteLengthText = Number.isFinite(sourceByteLength) && sourceByteLength > 0
  238. ? `${Math.floor(sourceByteLength)}B`
  239. : ''
  240. const parts = [
  241. register.sourceMemoryArea,
  242. register.sourceAddressText,
  243. sourceByteLengthText,
  244. bitText,
  245. register.sourceSymbolType && register.sourceSymbolType !== '---' ? register.sourceSymbolType : ''
  246. ].filter(Boolean)
  247. return parts.join(' · ')
  248. }
  249. function createGroupSourceMetaText(group) {
  250. const parts = [
  251. group.sourceMemoryArea,
  252. group.sourceAddressText,
  253. group.sourceSymbolName,
  254. group.sourceSegmentModule
  255. ].filter(Boolean)
  256. return parts.join(' · ')
  257. }
  258. function formatCompactNumber(value, precision = 4) {
  259. const text = formatFixedValue(value, precision)
  260. return text === '--' ? '--' : text.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
  261. }
  262. function formatCardMetricValue(value) {
  263. if (value === '' || value === null || value === undefined) return ''
  264. return formatCompactNumber(value)
  265. }
  266. function readOptionalNumber(value) {
  267. if (value === '' || value === null || value === undefined) return null
  268. const numberValue = Number(value)
  269. return Number.isFinite(numberValue) ? numberValue : null
  270. }
  271. function addMetricItem(items, value, unit = '') {
  272. const text = formatCardMetricValue(value)
  273. if (!text) return
  274. items.push({
  275. text: `${text}${unit}`
  276. })
  277. }
  278. function assignOptionalNumber(target, key, value) {
  279. if (value === null || value === undefined) return
  280. target[key] = value
  281. }
  282. function normalizeMemoryEndian(value, fallback = 'big') {
  283. const text = String(value || '').trim().toLowerCase()
  284. if (text === 'little' || text === 'le' || text === '1') return 'little'
  285. if (text === 'big' || text === 'be' || text === '0') return 'big'
  286. return fallback
  287. }
  288. function normalizeStorageCodeInfoCard(codeInfo = null) {
  289. const hasCodeInfo = !!codeInfo && codeInfo.hasCodeInfo !== false
  290. const refVolt = hasCodeInfo ? readOptionalNumber(codeInfo.refVolt) : null
  291. const refVoltRaw = hasCodeInfo ? readOptionalNumber(codeInfo.refVoltRaw) : null
  292. const card = {
  293. alongDiv: hasCodeInfo ? readOptionalNumber(codeInfo.alongDiv) : null,
  294. ampGain: hasCodeInfo ? readOptionalNumber(codeInfo.ampGain) : null,
  295. busDiv: hasCodeInfo ? readOptionalNumber(codeInfo.busDiv) : null,
  296. caveFreq: hasCodeInfo ? readOptionalNumber(codeInfo.caveFreq) : null,
  297. chipModel: hasCodeInfo ? String(codeInfo.chipModel || '').trim() : '',
  298. maxPacketLength: hasCodeInfo ? (Number(codeInfo.maxPacketLength) || 0) : 0,
  299. memoryEndian: hasCodeInfo ? normalizeMemoryEndian(codeInfo.memoryEndian) : 'big',
  300. memoryEndianRaw: hasCodeInfo ? (Number(codeInfo.memoryEndianRaw) || 0) : 0,
  301. model: hasCodeInfo ? String(codeInfo.model || '').trim() : '',
  302. refVolt: hasCodeInfo
  303. ? (refVolt !== null ? refVolt : (refVoltRaw !== null ? refVoltRaw / 10 : null))
  304. : null,
  305. refVoltRaw: refVoltRaw === null ? 0 : refVoltRaw,
  306. rsShunt: hasCodeInfo ? readOptionalNumber(codeInfo.rsShunt) : null,
  307. addressWidth: hasCodeInfo
  308. ? (Number(codeInfo.addressWidth || codeInfo.codeInfoAddressWidth || codeInfo.storageAddressWidth) || 0)
  309. : 0
  310. }
  311. const codeInfoContext = hasCodeInfo
  312. ? {
  313. addressWidth: card.addressWidth,
  314. codeInfoAddressWidth: card.addressWidth,
  315. maxPacketLength: card.maxPacketLength,
  316. memoryEndian: card.memoryEndian,
  317. memoryEndianRaw: card.memoryEndianRaw,
  318. storageAddressWidth: card.addressWidth
  319. }
  320. : {}
  321. if (hasCodeInfo) {
  322. assignOptionalNumber(codeInfoContext, 'alongDiv', card.alongDiv)
  323. assignOptionalNumber(codeInfoContext, 'ampGain', card.ampGain)
  324. assignOptionalNumber(codeInfoContext, 'busDiv', card.busDiv)
  325. assignOptionalNumber(codeInfoContext, 'caveFreq', card.caveFreq)
  326. assignOptionalNumber(codeInfoContext, 'refVolt', card.refVolt)
  327. assignOptionalNumber(codeInfoContext, 'rsShunt', card.rsShunt)
  328. }
  329. const metricItems = []
  330. addMetricItem(metricItems, card.caveFreq, 'KHz')
  331. addMetricItem(metricItems, card.refVolt, 'V')
  332. addMetricItem(metricItems, card.ampGain)
  333. addMetricItem(metricItems, card.rsShunt, 'mΩ')
  334. return {
  335. ...card,
  336. codeInfoContext,
  337. hasCodeInfo,
  338. metricItems
  339. }
  340. }
  341. function isStorageStructGroup(group = {}) {
  342. return isStructLayout(group.layout)
  343. && isByteAddressedGroup(group)
  344. && !!String(group.sourceMemoryArea || '').trim()
  345. }
  346. function isStorageMemoryGroup(group = {}) {
  347. return isByteAddressedGroup(group)
  348. && !!String(group.sourceMemoryArea || '').trim()
  349. }
  350. function isStorageScalarEntryKind(entryKind) {
  351. const text = String(entryKind || '').trim().toLowerCase()
  352. return text === 'enum' || text === 'variable'
  353. }
  354. function isStorageScalarGroup(group = {}) {
  355. return isStorageMemoryGroup(group)
  356. && !isStructLayout(group.layout)
  357. && isStorageScalarEntryKind(group.sourceEntryKind)
  358. }
  359. function isStorageSourceLockedGroup(group = {}) {
  360. return isStorageMemoryGroup(group)
  361. }
  362. function isStorageSourceLockedRegister(group = {}, register = {}) {
  363. return isStorageSourceLockedGroup(group) || isStorageMemoryGroup(register)
  364. }
  365. function isParameterGroupPollEnabled(group = {}) {
  366. return group.pollEnabled !== false
  367. }
  368. function getStorageAreaText(group = {}, lowercase = false) {
  369. const area = String(group.sourceMemoryArea || '').trim()
  370. return lowercase ? area.toLowerCase() : area.toUpperCase()
  371. }
  372. function stripStorageAreaPrefix(text) {
  373. const source = String(text || '').trim()
  374. const cleaned = source.replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '').trim()
  375. return cleaned || source
  376. }
  377. function formatRegisterStartAddressText(address) {
  378. return `0x${padWordHex(address)}`
  379. }
  380. function formatStorageStartAddressText(address, addressWidth = 0) {
  381. return `0x${padStorageHex(address, addressWidth)}`
  382. }
  383. function formatStartAddressText(address, maxAddress = MAX_MODBUS_ADDRESS) {
  384. return `0x${padHex(address, maxAddress > MAX_MODBUS_ADDRESS ? 8 : 4)}`
  385. }
  386. function formatStorageHexBytes(bytes = [], byteLength = 1) {
  387. const safeLength = Math.max(1, Math.floor(Number(byteLength) || 1))
  388. const source = Array.isArray(bytes) ? bytes : []
  389. return `0x${Array.from({ length: safeLength }, (_, index) => (
  390. (Number(source[index] || 0) & 0xFF).toString(16).toUpperCase().padStart(2, '0')
  391. )).join('')}`
  392. }
  393. function formatStorageRegisterHexValue(register, rawBytes = []) {
  394. if (isTextRegister(register.dataType)) {
  395. return formatRawByteTextWithDefault(rawBytes, register.byteLength)
  396. }
  397. return formatStorageHexBytes(rawBytes, register.byteLength)
  398. }
  399. function getStorageRegisterTypeText(register = {}) {
  400. const enumType = String(register.enumName || '').trim()
  401. if (enumType) return enumType
  402. const entryKind = String(register.sourceEntryKind || '').trim().toLowerCase()
  403. const sourceValueType = String(register.sourceValueType || register.sourceElementType || '').trim().toLowerCase()
  404. const isAggregateField = entryKind === 'struct' || sourceValueType === 'struct'
  405. const valueTypeCandidates = [
  406. register.dataTypeText,
  407. register.dataType
  408. ]
  409. const sourceTypeCandidates = [
  410. register.sourceDefinitionName,
  411. register.sourceSymbolType,
  412. register.sourceValueType,
  413. register.sourceElementType
  414. ]
  415. const candidates = isAggregateField
  416. ? valueTypeCandidates.concat(sourceTypeCandidates)
  417. : sourceTypeCandidates.concat(valueTypeCandidates)
  418. for (const value of candidates) {
  419. const text = String(value || '').trim()
  420. if (text && text !== '---') return text
  421. }
  422. return ''
  423. }
  424. function findEnumOptionByValue(register = {}) {
  425. const rawValue = Number(register.rawValue)
  426. if (!Number.isFinite(rawValue)) return null
  427. return normalizeEnumOptions(register).find((option) => (
  428. Number(option && option.value) === rawValue
  429. )) || null
  430. }
  431. function findEnumOptionByInputValue(register = {}) {
  432. const valueText = normalizeTextValue(register.inputValue).trim()
  433. if (!valueText || valueText === '--') return null
  434. let parsedValue = parseEnumValueText(register, valueText)
  435. if (parsedValue === null) parsedValue = parseNumberText(valueText, register.dataType)
  436. if (parsedValue === null) return null
  437. return normalizeEnumOptions(register).find((option) => (
  438. Number(option.value) === Number(parsedValue)
  439. )) || null
  440. }
  441. function findScalarEnumOption(register = {}) {
  442. if (register.isDirty) return findEnumOptionByInputValue(register)
  443. if (!hasReadScalarValue(register)) return null
  444. return findEnumOptionByValue(register)
  445. }
  446. function hasReadScalarValue(register = {}) {
  447. return register.rawValue !== null
  448. && register.rawValue !== undefined
  449. && !Array.isArray(register.rawValue)
  450. }
  451. function isEnumSourceText(value) {
  452. const text = String(value || '').trim().toLowerCase()
  453. return text === 'enum' || /^enum\b/.test(text) || /\benum\b/.test(text)
  454. }
  455. function isEnumLikeRegister(group = {}, register = {}) {
  456. if (String(register.enumName || '').trim()) return true
  457. if (normalizeEnumOptions(register).length) return true
  458. return [
  459. group.sourceEntryKind,
  460. group.sourceElementType,
  461. group.sourceValueType,
  462. group.sourceSymbolType,
  463. register.sourceEntryKind,
  464. register.sourceElementType,
  465. register.sourceValueType,
  466. register.sourceSymbolType
  467. ].some(isEnumSourceText)
  468. }
  469. function formatScalarDecimalValue(register = {}) {
  470. const rawValue = register.rawValue
  471. if (rawValue === null || rawValue === undefined || Array.isArray(rawValue)) return ''
  472. const enumOption = findEnumOptionByValue(register)
  473. if (enumOption) return `${enumOption.label || enumOption.name} (${Number(rawValue)})`
  474. const dataType = getDataType(register.dataType).key
  475. if (/^(?:u?int)(?:64|128|256)_t$/.test(dataType)) {
  476. return normalizeTextValue(rawValue || register.displayValue || '--') || '--'
  477. }
  478. const numberValue = Number(rawValue)
  479. if (!Number.isFinite(numberValue)) return normalizeTextValue(register.displayValue || '--') || '--'
  480. return (dataType === 'float' || dataType === 'double')
  481. ? formatCompactNumber(numberValue, 6)
  482. : String(Math.trunc(numberValue))
  483. }
  484. function formatScalarInputValue(register = {}) {
  485. const dirtyInputValue = normalizeTextValue(register.inputValue).trim()
  486. if (register.isDirty) return dirtyInputValue
  487. if (!hasReadScalarValue(register)) return ''
  488. const rawValue = register.rawValue
  489. if (typeof rawValue === 'bigint') return rawValue.toString()
  490. if (typeof rawValue === 'number') {
  491. if (!Number.isFinite(rawValue)) return ''
  492. return Number.isInteger(rawValue) ? String(rawValue) : formatCompactNumber(rawValue, 6)
  493. }
  494. return normalizeTextValue(rawValue).trim()
  495. }
  496. function createScalarDefinitionText(register = {}, group = {}) {
  497. const enumOption = findEnumOptionByValue(register)
  498. const typeName = String(register.enumName || register.sourceSymbolType || group.sourceSymbolType || '').trim()
  499. const dataTypeText = register.dataType === 'raw' ? '' : (register.dataTypeText || register.dataType)
  500. const definitionText = typeName || dataTypeText
  501. if (enumOption && definitionText) return `${definitionText}:${enumOption.label || enumOption.name}`
  502. if (enumOption) return enumOption.label || enumOption.name
  503. return definitionText
  504. }
  505. function createScalarEnumDefinitionText(register = {}, group = {}) {
  506. if (!normalizeEnumOptions(register).length) return ''
  507. const enumOption = findScalarEnumOption(register)
  508. if (!enumOption) return ''
  509. return enumOption.label || enumOption.name
  510. }
  511. function normalizeRegister(register, group, index, address, byteOffset = 0) {
  512. const registerType = getRegisterType(group.registerType).key
  513. const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  514. const isStorageMemory = isStorageMemoryGroup(group)
  515. const sourceMetadataLocked = isStorageSourceLockedRegister(group, register)
  516. const byteAddressed = isByteAddressedGroup(group)
  517. const dataType = normalizeRegisterDataType(register, registerType)
  518. const bitOffset = normalizeBitOffset(register.bitOffset)
  519. const bitWidth = normalizeBitWidth(register.bitWidth)
  520. const isBitField = !isBitRegisterType(registerType) && isBitFieldRegister(register)
  521. const isPlaceholderByteField = !!register.isPlaceholderByteField
  522. const textByteLength = isTextRegister(dataType)
  523. ? normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  524. : ''
  525. const defaultValue = normalizeTextValue(register.defaultValue)
  526. const savedValue = getRegisterSavedValue(register)
  527. const inputValue = savedValue === null ? defaultValue : savedValue
  528. const rawValue = register.rawValue === undefined ? null : register.rawValue
  529. const memoryEndian = isStorageMemory
  530. ? normalizeMemoryEndian(register.memoryEndian || group.codeInfoContext && group.codeInfoContext.memoryEndian)
  531. : 'big'
  532. const storageAddressWidth = isStorageMemory
  533. ? resolveStorageAddressWidth({
  534. ...group,
  535. sourceAddressByteLength: register.sourceAddressByteLength || group.sourceAddressByteLength,
  536. sourceAddressWidth: register.sourceAddressWidth || group.sourceAddressWidth,
  537. sourceMemoryArea: register.sourceMemoryArea || group.sourceMemoryArea
  538. }, address)
  539. : 0
  540. const byteLength = isBitRegisterType(registerType)
  541. ? 1
  542. : getRegisterByteLength(dataType, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
  543. const registerCount = isBitRegisterType(registerType)
  544. ? 1
  545. : getRegisterWordCountAtOffset(dataType, byteOffset, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
  546. const canShowUnit = !isBitRegisterType(registerType) && !isBitField && supportsUnit(dataType)
  547. const rawWords = Array.isArray(register.rawWords)
  548. ? register.rawWords.slice(0, registerCount).map((word) => Number(word) & 0xFFFF)
  549. : []
  550. const rawBytes = Array.isArray(register.rawBytes)
  551. ? register.rawBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF)
  552. : []
  553. const defaultRawValueText = rawValue === null
  554. ? '--'
  555. : (isBitRegisterType(registerType)
  556. ? formatCoilDisplayValue(rawValue)
  557. : (byteAddressed ? formatRawByteText(rawBytes) : formatRawWordText(rawWords)))
  558. const hasRawValue = rawValue !== null && rawValue !== undefined
  559. const rawValueText = isStorageMemory
  560. ? (hasRawValue ? formatStorageRegisterHexValue({
  561. ...register,
  562. byteLength,
  563. dataType
  564. }, rawBytes) : '')
  565. : defaultRawValueText
  566. const displayValue = rawValue === null
  567. ? (inputValue.trim() ? inputValue : '--')
  568. : formatRegisterValue({ ...register, dataType, byteOffset, memoryEndian }, rawValue)
  569. const conversionFormula = normalizeTextValue(register.conversionFormula).trim()
  570. const conversionResult = conversionFormula && rawValue !== null
  571. ? evaluateValueFormula(conversionFormula, rawValue, group.codeInfoContext || {})
  572. : null
  573. const convertedDisplayValue = conversionResult && conversionResult.ok
  574. ? conversionResult.text
  575. : displayValue
  576. const displayMetaText = conversionResult && conversionResult.ok
  577. ? `raw ${displayValue}`
  578. : ''
  579. const addressRangeText = isBitRegisterType(registerType)
  580. ? `0x${padHex(address)}`
  581. : (byteAddressed
  582. ? (isStorageMemory
  583. ? formatStorageAddressRange(address, Math.max(1, byteLength), storageAddressWidth)
  584. : formatAddressRange(address, Math.max(1, byteLength)))
  585. : formatAddressRange(address, registerCount))
  586. const addressText = isBitField
  587. ? (byteAddressed
  588. ? `${isStorageMemory
  589. ? formatStorageAddressRange(address, Math.max(1, byteLength), storageAddressWidth)
  590. : formatAddressRange(address, Math.max(1, byteLength))}.b${bitOffset}${bitWidth > 1 ? `..b${bitOffset + bitWidth - 1}` : ''}`
  591. : formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth))
  592. : (byteAddressed
  593. ? (isStorageMemory
  594. ? formatStorageAddressRange(address, Math.max(1, byteLength), storageAddressWidth)
  595. : formatAddressRange(address, Math.max(1, byteLength)))
  596. : formatRegisterAddressText(address, byteOffset, byteLength, registerType))
  597. const registerStartAddressText = isStorageMemory
  598. ? formatStorageStartAddressText(address, storageAddressWidth)
  599. : formatRegisterStartAddressText(address)
  600. const registerTypeText = getStorageRegisterTypeText({
  601. ...register,
  602. dataType,
  603. dataTypeText: getRegisterValueTypeLabel(dataType)
  604. })
  605. const metaText = isStorageMemory
  606. ? [registerStartAddressText, registerTypeText, rawValueText].filter(Boolean).join(' ')
  607. : `${addressText} ${rawValueText}`.trim()
  608. const cardMetaText = isStorageMemory
  609. ? metaText
  610. : (sourceMetadataLocked ? '' : metaText)
  611. const name = register.name || `寄存器 ${index + 1}`
  612. return {
  613. address,
  614. addressRangeText,
  615. addressText,
  616. bitOffset: isBitField ? bitOffset : '',
  617. bitWidth: isBitField ? bitWidth : '',
  618. byteLength,
  619. byteLengthText: isBitRegisterType(registerType)
  620. ? '1bit'
  621. : (isBitField
  622. ? (bitWidth === 1 ? `1bit/占${byteLength}B` : `${bitWidth}bit/占${byteLength}B`)
  623. : (textByteLength && textByteLength !== byteLength ? `${textByteLength}B/占${byteLength}B` : `${byteLength}B`)),
  624. byteStart: Math.max(0, Math.floor(Number(register.byteStart) || 0)),
  625. dataType,
  626. dataTypeIndex: getDataTypeIndex(dataType),
  627. dataTypeText: getRegisterValueTypeLabel(dataType),
  628. defaultValue,
  629. displayMetaText,
  630. displayValue: convertedDisplayValue,
  631. id: register.id || createId('gm-reg'),
  632. inputType: isTextRegister(dataType) ? 'text' : 'text',
  633. inputValue,
  634. isStructField: layout === GROUP_LAYOUT_STRUCT || !!register.isStructField,
  635. layout,
  636. isBitField,
  637. isPlaceholderByteField,
  638. isDirty: !!register.isDirty,
  639. maxValue: normalizeTextValue(register.maxValue),
  640. displayName: name,
  641. metaText: cardMetaText,
  642. minValue: normalizeTextValue(register.minValue),
  643. memoryEndian,
  644. name,
  645. conversionFormula,
  646. conversionFormulaErrorText: conversionResult && conversionResult.errorText ? conversionResult.errorText : '',
  647. rawValue,
  648. rawValueText,
  649. rawBytes,
  650. rawWords,
  651. registerCount,
  652. registerStartAddressText,
  653. registerTypeText,
  654. byteOffset,
  655. registerType,
  656. showDataType: !isBitRegisterType(registerType),
  657. showRange: !isBitRegisterType(registerType) && !isBitField && supportsRange(dataType),
  658. showTextLength: !isBitRegisterType(registerType) && isTextRegister(dataType),
  659. showUnit: canShowUnit,
  660. textByteLength,
  661. unit: canShowUnit ? normalizeTextValue(register.unit).trim() : '',
  662. structByteLength: register.structByteLength,
  663. remark: register.remark || '',
  664. ...pickFields(register, SOURCE_REGISTER_FIELDS),
  665. sourceMetaText: createRegisterSourceMetaText(register),
  666. sourceMetadataLocked
  667. }
  668. }
  669. function normalizeGroup(group) {
  670. const registerType = getRegisterType(group.registerType || group.type || DEFAULT_REGISTER_TYPE)
  671. const sourceMemoryArea = String(group.sourceMemoryArea || '').trim()
  672. const isStorageGroupSource = !!sourceMemoryArea || group.addressUnit === 'byte' || group.addressUnit === 'bytes'
  673. const startAddress = isStorageGroupSource
  674. ? normalizeStorageAddress(
  675. group.sourceAddress !== undefined && group.sourceAddress !== null && group.sourceAddress !== ''
  676. ? group.sourceAddress
  677. : group.startAddress,
  678. 0
  679. )
  680. : normalizeAddress(group.startAddress, 0)
  681. const maxQuantity = getMaxQuantity(registerType.key)
  682. const sourceRegisters = Array.isArray(group.registers) ? group.registers : []
  683. const codeInfoContext = group.codeInfoContext || {}
  684. const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  685. const byteAddressed = isByteAddressedGroup(group)
  686. const hasExplicitQuantity = group.quantity !== undefined && group.quantity !== null && group.quantity !== ''
  687. const quantity = hasExplicitQuantity
  688. ? clampInteger(group.quantity, 1, maxQuantity, 1)
  689. : clampInteger(sourceRegisters.length || 1, 1, maxQuantity, 1)
  690. const baseGroup = {
  691. deleteVisible: !!group.deleteVisible,
  692. expanded: group.expanded === true,
  693. id: group.id || createId('gm-group'),
  694. isStructLayout: layout === GROUP_LAYOUT_STRUCT,
  695. layout,
  696. name: String(group.name || group.groupName || '寄存器组').trim() || '寄存器组',
  697. pollEnabled: group.pollEnabled === false ? false : true,
  698. quantity,
  699. registerType: registerType.key,
  700. startAddress,
  701. touchStartX: 0,
  702. codeInfoContext,
  703. ...pickFields(group, SOURCE_GROUP_FIELDS)
  704. }
  705. const registers = []
  706. let nextAddress = startAddress
  707. let nextByteOffset = 0
  708. for (let index = 0; index < quantity; index += 1) {
  709. const sourceRegister = sourceRegisters[index] || {}
  710. let normalizedSourceRegister = sourceRegister
  711. const dataType = normalizeRegisterDataType(sourceRegister, baseGroup.registerType)
  712. const textByteLength = isTextRegister(dataType)
  713. ? normalizeTextByteLength(sourceRegister.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  714. : ''
  715. const isBitRegister = isBitRegisterType(baseGroup.registerType)
  716. let address = startAddress + index
  717. let byteOffset = 0
  718. if (!isBitRegister) {
  719. const explicitByteStart = Number(sourceRegister.byteStart)
  720. const hasExplicitByteStart = layout === GROUP_LAYOUT_STRUCT && Number.isFinite(explicitByteStart)
  721. const byteLength = getRegisterByteLength(dataType, { ...sourceRegister, layout, textByteLength })
  722. if (layout !== GROUP_LAYOUT_STRUCT && !isByteRegister(dataType) && nextByteOffset % 2 !== 0) {
  723. nextByteOffset += 1
  724. }
  725. const currentByteStart = hasExplicitByteStart
  726. ? Math.max(0, Math.floor(explicitByteStart))
  727. : nextByteOffset
  728. address = byteAddressed ? startAddress + currentByteStart : startAddress + Math.floor(currentByteStart / 2)
  729. byteOffset = byteAddressed ? 0 : currentByteStart % 2
  730. normalizedSourceRegister = {
  731. ...sourceRegister,
  732. byteStart: currentByteStart
  733. }
  734. nextByteOffset = Math.max(nextByteOffset, currentByteStart + byteLength)
  735. }
  736. const register = normalizeRegister(normalizedSourceRegister, baseGroup, index, address, byteOffset)
  737. registers.push(register)
  738. if (isBitRegister) nextAddress += register.registerCount
  739. }
  740. const byteLength = isBitRegisterType(baseGroup.registerType)
  741. ? Math.max(1, nextAddress - startAddress)
  742. : Math.max(1, nextByteOffset)
  743. const paddedByteLength = isBitRegisterType(baseGroup.registerType)
  744. ? byteLength
  745. : (byteAddressed ? byteLength : alignEvenByteLength(byteLength))
  746. const wordQuantity = isBitRegisterType(baseGroup.registerType)
  747. ? Math.max(1, nextAddress - startAddress)
  748. : (byteAddressed ? Math.max(1, byteLength) : Math.max(1, paddedByteLength / 2))
  749. const addressSpan = byteAddressed ? Math.max(1, byteLength) : wordQuantity
  750. const storageMemory = isStorageMemoryGroup(baseGroup)
  751. const storageScalarGroup = isStorageScalarGroup(baseGroup)
  752. const sourceMetadataLocked = isStorageSourceLockedGroup(baseGroup)
  753. const storageAddressWidth = storageMemory ? resolveStorageAddressWidth(baseGroup, startAddress) : 0
  754. const storageAddressMax = getStorageAddressMax(storageAddressWidth, startAddress)
  755. const addressOverflow = storageMemory
  756. ? isStorageAddressRangeOverflow(startAddress, addressSpan, storageAddressWidth)
  757. : isAddressRangeOverflow(startAddress, addressSpan)
  758. const endAddress = startAddress + addressSpan - 1
  759. const addressMax = storageMemory ? storageAddressMax : MAX_MODBUS_ADDRESS
  760. const startAddressText = storageMemory
  761. ? formatStorageStartAddressText(startAddress, storageAddressWidth)
  762. : formatStartAddressText(startAddress, addressMax)
  763. const endAddressText = storageMemory
  764. ? (addressOverflow ? `0x${padStorageHex(addressMax, storageAddressWidth)}+` : `0x${padStorageHex(endAddress, storageAddressWidth)}`)
  765. : (addressOverflow ? `0x${padWordHex(addressMax)}+` : `0x${padWordHex(endAddress)}`)
  766. const scalarRegister = storageScalarGroup ? (registers[0] || null) : null
  767. const scalarDefinitionText = scalarRegister ? createScalarDefinitionText(scalarRegister, baseGroup) : ''
  768. const scalarEnumDefinitionText = scalarRegister ? createScalarEnumDefinitionText(scalarRegister, baseGroup) : ''
  769. const scalarInputValueText = scalarRegister ? formatScalarInputValue(scalarRegister) : ''
  770. const scalarRawHexText = scalarRegister ? scalarRegister.rawValueText : ''
  771. const scalarValueText = scalarRegister ? formatScalarDecimalValue(scalarRegister) : ''
  772. const isStorageEnumScalar = !!(storageScalarGroup && scalarRegister && isEnumLikeRegister(baseGroup, scalarRegister))
  773. const displayName = storageMemory
  774. ? stripStorageAreaPrefix(baseGroup.name)
  775. : baseGroup.name
  776. const storageCardMetaParts = [startAddressText]
  777. if (storageScalarGroup && scalarEnumDefinitionText) {
  778. storageCardMetaParts.push(scalarEnumDefinitionText)
  779. }
  780. const storageCardMetaText = storageMemory
  781. ? storageCardMetaParts.filter(Boolean).join(' ')
  782. : ''
  783. const listMetaText = storageMemory
  784. ? (sourceMetadataLocked
  785. ? ''
  786. : (storageScalarGroup
  787. ? [
  788. getStorageAreaText(baseGroup),
  789. startAddressText,
  790. `${byteLength}B`,
  791. scalarRawHexText,
  792. scalarDefinitionText
  793. ].filter(Boolean).join(' ')
  794. : `${getStorageAreaText(baseGroup)} ${startAddressText}-${endAddressText} ${byteLength}B`))
  795. : `${formatAddressRange(startAddress, addressSpan)} · ${quantity}/${wordQuantity}${createGroupSourceMetaText(baseGroup) ? ` · ${createGroupSourceMetaText(baseGroup)}` : ''}${addressOverflow ? ' · 地址超出 0xFFFF' : ''}`
  796. const detailMetaText = storageMemory
  797. ? (sourceMetadataLocked ? '' : `${getStorageAreaText(baseGroup, true)} ${startAddressText} ${quantity}/${byteLength}B`)
  798. : `${formatAddressRange(startAddress, addressSpan)} · ${quantity}/${wordQuantity}${createGroupSourceMetaText(baseGroup) ? ` · ${createGroupSourceMetaText(baseGroup)}` : ''}${addressOverflow ? ' · 地址超出 0xFFFF' : ''}`
  799. const sourceMetaText = createGroupSourceMetaText(baseGroup)
  800. return {
  801. ...baseGroup,
  802. addressRangeText: storageMemory
  803. ? formatStorageAddressRange(startAddress, addressSpan, storageAddressWidth)
  804. : formatAddressRange(startAddress, addressSpan),
  805. addressOverflow,
  806. addressWarningText: addressOverflow
  807. ? `地址超出 0x${storageMemory ? padStorageHex(addressMax, storageAddressWidth) : padWordHex(addressMax)}`
  808. : '',
  809. detailMetaText,
  810. detailTitleText: displayName,
  811. displayName,
  812. endAddressText,
  813. functionCode: registerType.functionCode,
  814. isReadOnly: !registerType.writable,
  815. isStorageEnumScalar,
  816. isStorageScalarGroup: storageScalarGroup,
  817. byteLength,
  818. listMetaText,
  819. maxQuantity,
  820. registerTypeIndex: getRegisterTypeIndex(registerType.key),
  821. registerTypeText: registerType.label,
  822. registers,
  823. paddedByteLength,
  824. scalarDefinitionText,
  825. scalarEnumDefinitionText,
  826. scalarInputValueText,
  827. scalarRawHexText,
  828. scalarValueText,
  829. sourceMetaText,
  830. sourceMetadataLocked,
  831. startAddressText,
  832. storageCardMetaText,
  833. wordQuantity,
  834. writable: registerType.writable
  835. }
  836. }
  837. function normalizeGroupConfig(config = {}) {
  838. const registerType = config.registerTypeIndex !== undefined && config.registerTypeIndex !== null
  839. ? (REGISTER_TYPE_OPTIONS[Number(config.registerTypeIndex)] || REGISTER_TYPE_OPTIONS[0])
  840. : getRegisterType(config.registerType || config.type || DEFAULT_REGISTER_TYPE)
  841. const maxQuantity = getMaxQuantity(registerType.key)
  842. const isStorageConfig = !!String(config.sourceMemoryArea || '').trim()
  843. || config.addressUnit === 'byte'
  844. || config.addressUnit === 'bytes'
  845. const storageAddressWidth = isStorageConfig ? resolveStorageAddressWidth(config, 0) : 0
  846. const storageAddressByteLength = storageAddressWidth === 16 ? 2 : 4
  847. return {
  848. ...(isStorageConfig ? {
  849. addressUnit: config.addressUnit || 'byte',
  850. sourceAddressByteLength: config.sourceAddressByteLength || storageAddressByteLength,
  851. sourceAddressWidth: storageAddressWidth,
  852. sourceMemoryArea: config.sourceMemoryArea || 'XDATA'
  853. } : {}),
  854. layout: isStructLayout(config.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
  855. name: String(config.name || config.groupName || '寄存器组').trim() || '寄存器组',
  856. pollEnabled: config.pollEnabled === false ? false : true,
  857. quantity: parseConfigQuantity(config.quantity, maxQuantity),
  858. registerType: registerType.key,
  859. startAddress: parseConfigAddress(config.startAddress, {
  860. label: isStorageConfig ? '内存起始地址' : '寄存器起始地址',
  861. maxAddress: isStorageConfig ? getStorageAddressMax(storageAddressWidth) : MAX_MODBUS_ADDRESS
  862. })
  863. }
  864. }
  865. function getRegisterJsonValue(register) {
  866. if (register.inputValue !== undefined && register.inputValue !== null) {
  867. return normalizeTextValue(register.inputValue)
  868. }
  869. if (register.defaultValue !== undefined && register.defaultValue !== null) {
  870. return normalizeTextValue(register.defaultValue)
  871. }
  872. if (register.displayValue !== undefined && register.displayValue !== null && register.displayValue !== '--') {
  873. return normalizeTextValue(register.displayValue)
  874. }
  875. return ''
  876. }
  877. function normalizeImportedRegisterDataType(register) {
  878. const dataType = register.dataType || register.type || DEFAULT_DATA_TYPE
  879. return getDataType(dataType).key
  880. }
  881. function cloneImportedGroup(group) {
  882. return {
  883. layout: isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
  884. name: group.name,
  885. pollEnabled: group.pollEnabled === false ? false : true,
  886. quantity: group.quantity,
  887. registerType: group.registerType || group.type || DEFAULT_REGISTER_TYPE,
  888. registers: (Array.isArray(group.registers) ? group.registers : []).map((register) => ({
  889. conversionFormula: register.conversionFormula,
  890. dataType: normalizeImportedRegisterDataType(register),
  891. defaultValue: register.defaultValue,
  892. inputValue: register.inputValue,
  893. maxValue: register.maxValue,
  894. minValue: register.minValue,
  895. name: register.name,
  896. isStructField: !!register.isStructField,
  897. textByteLength: register.textByteLength,
  898. remark: register.remark,
  899. unit: register.unit,
  900. value: register.value,
  901. ...pickFields(register, STRUCT_REGISTER_FIELDS),
  902. ...pickFields(register, SOURCE_REGISTER_FIELDS)
  903. })),
  904. startAddress: group.startAddress,
  905. ...pickFields(group, SOURCE_GROUP_FIELDS)
  906. }
  907. }
  908. module.exports = {
  909. DATA_TYPE_OPTIONS,
  910. DEFAULT_DATA_TYPE,
  911. DEFAULT_REGISTER_TYPE,
  912. GROUP_LAYOUT_REGISTER,
  913. GROUP_LAYOUT_STRUCT,
  914. MAX_MODBUS_ADDRESS,
  915. MAX_STORAGE_ADDRESS,
  916. REGISTER_TYPE_OPTIONS,
  917. cloneImportedGroup,
  918. decodeRegisterFromByteCache,
  919. decodeRegisterFromWordCache,
  920. decodeRegisterValue,
  921. formatCoilDisplayValue,
  922. formatRegisterValue,
  923. getDataType,
  924. getRegisterEncodedBytes,
  925. getRegisterEncodedWords,
  926. getGroupEncodedBytes,
  927. getGroupEncodedWords,
  928. getRegisterWordCount,
  929. getRegisterJsonValue,
  930. getRegisterBytesFromByteCache,
  931. getRegisterWordsFromByteCache,
  932. getRegisterWordsFromWordCache,
  933. getRegisterWriteValueText,
  934. isAddressRangeOverflow,
  935. isBitRegisterType,
  936. isByteRegister,
  937. isParameterGroupPollEnabled,
  938. isStorageMemoryGroup,
  939. isStorageScalarGroup,
  940. isStorageSourceLockedGroup,
  941. isStorageSourceLockedRegister,
  942. isStorageStructGroup,
  943. isTextRegister,
  944. normalizeGroup,
  945. normalizeGroupConfig,
  946. normalizeStorageCodeInfoCard,
  947. normalizeRegister,
  948. parseCoilValue,
  949. registerTypeIsBit,
  950. splitWordSpans,
  951. validateRegisterValue
  952. }