1
0

model.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  1. const {
  2. clampInteger,
  3. createId,
  4. normalizeTextValue,
  5. padHex
  6. } = require('../../utils/base-utils.js')
  7. const {
  8. BYTE_ADDRESS_MEMORY_AREAS,
  9. DATA_TYPE_OPTIONS,
  10. DEFAULT_DATA_TYPE,
  11. DEFAULT_REGISTER_TYPE,
  12. DEFAULT_TEXT_BYTE_LENGTH,
  13. GROUP_LAYOUT_REGISTER,
  14. GROUP_LAYOUT_STRUCT,
  15. MAX_MODBUS_ADDRESS,
  16. MAX_PARAMETER_GROUP_ITEMS,
  17. REGISTER_TYPE_OPTIONS,
  18. SOURCE_GROUP_FIELDS,
  19. SOURCE_REGISTER_FIELDS,
  20. STRUCT_REGISTER_FIELDS
  21. } = require('./constants.js')
  22. const {
  23. formatFixedValue
  24. } = require('../../utils/number-format.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. normalizeBitOffset,
  47. normalizeBitWidth,
  48. normalizeTextByteLength,
  49. parseCoilValue,
  50. registerTypeIsBit,
  51. supportsRange,
  52. supportsUnit
  53. } = require('./value-codec.js')
  54. const {
  55. decodeRegisterFromByteCache,
  56. decodeRegisterFromWordCache,
  57. getGroupEncodedBytes,
  58. getGroupEncodedWords,
  59. getRegisterBytesFromByteCache,
  60. getRegisterEncodedBytes,
  61. getRegisterEncodedWords,
  62. getRegisterWordsFromByteCache,
  63. getRegisterWordsFromWordCache,
  64. getRegisterWriteValueText,
  65. splitWordSpans,
  66. validateRegisterValue
  67. } = require('./register-io.js')
  68. function normalizeAddress(value, fallback = 0) {
  69. if (typeof value === 'number') {
  70. return Number.isFinite(value) ? clampInteger(value, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  71. }
  72. const text = String(value === undefined || value === null ? '' : value).trim()
  73. if (!text) return fallback
  74. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  75. if (/^[0-9A-F]+$/i.test(hexText)) {
  76. const parsedHex = parseInt(hexText, 16)
  77. return Number.isFinite(parsedHex) ? clampInteger(parsedHex, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  78. }
  79. const numberValue = Number(text)
  80. return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  81. }
  82. function parseConfigAddress(value) {
  83. if (typeof value === 'number') {
  84. return clampInteger(value, 0, MAX_MODBUS_ADDRESS, 0)
  85. }
  86. const text = String(value === undefined || value === null ? '' : value).trim()
  87. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  88. if (!/^[0-9A-F]{1,4}$/i.test(hexText)) {
  89. throw new Error('寄存器起始地址无效')
  90. }
  91. return parseInt(hexText, 16)
  92. }
  93. function parseConfigQuantity(value, maxQuantity) {
  94. const text = String(value === undefined || value === null ? '' : value).trim()
  95. const quantity = Number(text)
  96. if (!Number.isInteger(quantity) || quantity < 1 || quantity > maxQuantity) {
  97. throw new Error(`寄存器数量需为 1 - ${maxQuantity}`)
  98. }
  99. return quantity
  100. }
  101. function getRegisterType(typeKey) {
  102. return REGISTER_TYPE_OPTIONS.find((item) => item.key === typeKey) || REGISTER_TYPE_OPTIONS[0]
  103. }
  104. function getRegisterTypeIndex(typeKey) {
  105. return Math.max(0, REGISTER_TYPE_OPTIONS.findIndex((item) => item.key === getRegisterType(typeKey).key))
  106. }
  107. function isStructLayout(layout) {
  108. return layout === GROUP_LAYOUT_STRUCT
  109. }
  110. function padWordHex(value) {
  111. return Number(value || 0).toString(16).toUpperCase().padStart(4, '0')
  112. }
  113. function formatAddressRange(startAddress, wordCount) {
  114. const address = normalizeAddress(startAddress, 0)
  115. const count = Math.max(1, Number(wordCount) || 1)
  116. const endAddress = address + count - 1
  117. const safeEndAddress = Math.min(endAddress, MAX_MODBUS_ADDRESS)
  118. const overflowText = endAddress > MAX_MODBUS_ADDRESS ? '+' : ''
  119. if (count <= 1) return `0x${padWordHex(address)}`
  120. return `0x${padWordHex(address)}-0x${padWordHex(safeEndAddress)}${overflowText}`
  121. }
  122. function isByteAddressedGroup(group = {}) {
  123. const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()
  124. return group.addressUnit === 'byte'
  125. || group.addressUnit === 'bytes'
  126. || BYTE_ADDRESS_MEMORY_AREAS.indexOf(memoryArea) >= 0
  127. }
  128. function formatRegisterAddressText(address, byteOffset, byteLength, registerType) {
  129. if (isBitRegisterType(registerType)) return `0x${padHex(address)}`
  130. if (byteLength === 1) return `0x${padHex(address)}${byteOffset === 0 ? 'H' : 'L'}`
  131. return `0x${padHex(address)}`
  132. }
  133. function formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth) {
  134. const byteText = formatRegisterAddressText(address, byteOffset, 1, DEFAULT_REGISTER_TYPE)
  135. const startBit = normalizeBitOffset(bitOffset)
  136. const endBit = startBit + normalizeBitWidth(bitWidth) - 1
  137. return endBit === startBit
  138. ? `${byteText}.b${startBit}`
  139. : `${byteText}.b${startBit}..${endBit}`
  140. }
  141. function isAddressRangeOverflow(startAddress, wordCount) {
  142. const address = normalizeAddress(startAddress, 0)
  143. const count = Math.max(1, Number(wordCount) || 1)
  144. return address + count - 1 > MAX_MODBUS_ADDRESS
  145. }
  146. function getMaxQuantity() {
  147. return MAX_PARAMETER_GROUP_ITEMS
  148. }
  149. function getRegisterSavedValue(register) {
  150. if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
  151. if (register.value !== undefined && register.value !== null) return normalizeTextValue(register.value)
  152. return null
  153. }
  154. function normalizeRegisterDataType(register, registerType) {
  155. if (isBitRegisterType(registerType)) return DEFAULT_DATA_TYPE
  156. return getDataType(register.dataType || register.type || DEFAULT_DATA_TYPE).key
  157. }
  158. function pickFields(source, fields) {
  159. return fields.reduce((result, field) => {
  160. if (source && source[field] !== undefined && source[field] !== null && source[field] !== '') {
  161. result[field] = source[field]
  162. }
  163. return result
  164. }, {})
  165. }
  166. function createRegisterSourceMetaText(register) {
  167. const bitText = isBitFieldRegister(register)
  168. ? `bit${normalizeBitOffset(register.bitOffset)}:${normalizeBitWidth(register.bitWidth)}`
  169. : ''
  170. const parts = [
  171. register.sourceMemoryArea,
  172. register.sourceAddressText,
  173. bitText,
  174. register.sourceSymbolType && register.sourceSymbolType !== '---' ? register.sourceSymbolType : ''
  175. ].filter(Boolean)
  176. return parts.join(' · ')
  177. }
  178. function createGroupSourceMetaText(group) {
  179. const parts = [
  180. group.sourceMemoryArea,
  181. group.sourceAddressText,
  182. group.sourceSymbolName,
  183. group.sourceSegmentModule
  184. ].filter(Boolean)
  185. return parts.join(' · ')
  186. }
  187. function formatCompactNumber(value, precision = 4) {
  188. const text = formatFixedValue(value, precision)
  189. return text === '--' ? '--' : text.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
  190. }
  191. function formatCardMetricValue(value) {
  192. if (value === '--') return '--'
  193. return formatCompactNumber(value)
  194. }
  195. function normalizeStorageCodeInfoCard(codeInfo = null) {
  196. const hasCodeInfo = !!codeInfo && codeInfo.hasCodeInfo !== false
  197. const refVolt = hasCodeInfo ? Number(codeInfo.refVolt) : NaN
  198. const card = {
  199. alongDiv: hasCodeInfo ? (Number(codeInfo.alongDiv) || 0) : 0,
  200. ampGain: hasCodeInfo ? (Number(codeInfo.ampGain) || 0) : '--',
  201. busDiv: hasCodeInfo ? (Number(codeInfo.busDiv) || 0) : 0,
  202. caveFreq: hasCodeInfo ? (Number(codeInfo.caveFreq) || 0) : '--',
  203. chipModel: hasCodeInfo ? (String(codeInfo.chipModel || '').trim() || '--') : '--',
  204. model: hasCodeInfo ? (String(codeInfo.model || '').trim() || '--') : '--',
  205. refVolt: hasCodeInfo
  206. ? (Number.isFinite(refVolt) ? refVolt : ((Number(codeInfo.refVoltRaw) || 0) / 10))
  207. : '--',
  208. refVoltRaw: hasCodeInfo ? (Number(codeInfo.refVoltRaw) || 0) : 0,
  209. rsShunt: hasCodeInfo ? (Number(codeInfo.rsShunt) || 0) : '--'
  210. }
  211. const codeInfoContext = hasCodeInfo
  212. ? {
  213. alongDiv: card.alongDiv,
  214. ampGain: card.ampGain,
  215. busDiv: card.busDiv,
  216. caveFreq: card.caveFreq,
  217. refVolt: card.refVolt,
  218. rsShunt: card.rsShunt
  219. }
  220. : {}
  221. return {
  222. ...card,
  223. codeInfoContext,
  224. hasCodeInfo,
  225. metricItems: [
  226. {
  227. text: `${formatCardMetricValue(card.caveFreq)}KHz`
  228. },
  229. {
  230. text: `${formatCardMetricValue(card.refVolt)}V`
  231. },
  232. {
  233. text: formatCardMetricValue(card.ampGain)
  234. },
  235. {
  236. text: `${formatCardMetricValue(card.rsShunt)}mΩ`
  237. }
  238. ]
  239. }
  240. }
  241. function isStorageStructGroup(group = {}) {
  242. return isStructLayout(group.layout)
  243. && isByteAddressedGroup(group)
  244. && !!String(group.sourceMemoryArea || '').trim()
  245. }
  246. function getStorageAreaText(group = {}, lowercase = false) {
  247. const area = String(group.sourceMemoryArea || '').trim()
  248. return lowercase ? area.toLowerCase() : area.toUpperCase()
  249. }
  250. function stripStorageAreaPrefix(text) {
  251. const source = String(text || '').trim()
  252. const cleaned = source.replace(/^(?:IDATA|XDATA|DATA|CODE)[\s:_-]+/i, '').trim()
  253. return cleaned || source
  254. }
  255. function formatRegisterStartAddressText(address) {
  256. return `0x${padHex(address)}`
  257. }
  258. function formatStorageHexBytes(bytes = [], byteLength = 1) {
  259. const safeLength = Math.max(1, Math.floor(Number(byteLength) || 1))
  260. const source = Array.isArray(bytes) ? bytes : []
  261. return `0x${Array.from({ length: safeLength }, (_, index) => (
  262. (Number(source[index] || 0) & 0xFF).toString(16).toUpperCase().padStart(2, '0')
  263. )).join('')}`
  264. }
  265. function formatStorageRegisterHexValue(register, rawBytes = []) {
  266. if (isTextRegister(register.dataType)) {
  267. return formatRawByteTextWithDefault(rawBytes, register.byteLength)
  268. }
  269. return formatStorageHexBytes(rawBytes, register.byteLength)
  270. }
  271. function normalizeRegister(register, group, index, address, byteOffset = 0) {
  272. const registerType = getRegisterType(group.registerType).key
  273. const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  274. const isStorageStruct = isStorageStructGroup(group)
  275. const byteAddressed = isByteAddressedGroup(group)
  276. const dataType = normalizeRegisterDataType(register, registerType)
  277. const bitOffset = normalizeBitOffset(register.bitOffset)
  278. const bitWidth = normalizeBitWidth(register.bitWidth)
  279. const isBitField = !isBitRegisterType(registerType) && isBitFieldRegister(register)
  280. const isPlaceholderByteField = !!register.isPlaceholderByteField
  281. const textByteLength = isTextRegister(dataType)
  282. ? normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  283. : ''
  284. const defaultValue = normalizeTextValue(register.defaultValue)
  285. const savedValue = getRegisterSavedValue(register)
  286. const inputValue = savedValue === null ? defaultValue : savedValue
  287. const rawValue = register.rawValue === undefined ? null : register.rawValue
  288. const byteLength = isBitRegisterType(registerType)
  289. ? 1
  290. : getRegisterByteLength(dataType, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
  291. const registerCount = isBitRegisterType(registerType)
  292. ? 1
  293. : getRegisterWordCountAtOffset(dataType, byteOffset, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
  294. const canShowUnit = !isBitRegisterType(registerType) && !isBitField && supportsUnit(dataType)
  295. const rawWords = Array.isArray(register.rawWords)
  296. ? register.rawWords.slice(0, registerCount).map((word) => Number(word) & 0xFFFF)
  297. : []
  298. const rawBytes = Array.isArray(register.rawBytes)
  299. ? register.rawBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF)
  300. : []
  301. const defaultRawValueText = rawValue === null
  302. ? '--'
  303. : (isBitRegisterType(registerType)
  304. ? formatCoilDisplayValue(rawValue)
  305. : (byteAddressed ? formatRawByteText(rawBytes) : formatRawWordText(rawWords)))
  306. const rawValueText = isStorageStruct
  307. ? formatStorageRegisterHexValue({
  308. ...register,
  309. byteLength,
  310. dataType
  311. }, rawBytes)
  312. : defaultRawValueText
  313. const displayValue = rawValue === null
  314. ? (inputValue.trim() ? inputValue : '--')
  315. : formatRegisterValue({ ...register, dataType, byteOffset }, rawValue)
  316. const conversionFormula = normalizeTextValue(register.conversionFormula).trim()
  317. const conversionResult = conversionFormula && rawValue !== null
  318. ? evaluateValueFormula(conversionFormula, rawValue, group.codeInfoContext || {})
  319. : null
  320. const convertedDisplayValue = conversionResult && conversionResult.ok
  321. ? conversionResult.text
  322. : displayValue
  323. const displayMetaText = conversionResult && conversionResult.ok
  324. ? `raw ${displayValue}`
  325. : ''
  326. const addressRangeText = isBitRegisterType(registerType)
  327. ? `0x${padHex(address)}`
  328. : (byteAddressed
  329. ? formatAddressRange(address, Math.max(1, byteLength))
  330. : formatAddressRange(address, registerCount))
  331. const addressText = isBitField
  332. ? (byteAddressed
  333. ? `${formatAddressRange(address, Math.max(1, byteLength))}.b${bitOffset}${bitWidth > 1 ? `..b${bitOffset + bitWidth - 1}` : ''}`
  334. : formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth))
  335. : (byteAddressed
  336. ? formatAddressRange(address, Math.max(1, byteLength))
  337. : formatRegisterAddressText(address, byteOffset, byteLength, registerType))
  338. const registerStartAddressText = formatRegisterStartAddressText(address)
  339. const metaText = isStorageStruct
  340. ? `${registerStartAddressText} ${rawValueText}`
  341. : `${addressText} ${rawValueText}`.trim()
  342. return {
  343. address,
  344. addressRangeText,
  345. addressText,
  346. bitOffset: isBitField ? bitOffset : '',
  347. bitWidth: isBitField ? bitWidth : '',
  348. byteLength,
  349. byteLengthText: isBitRegisterType(registerType)
  350. ? '1bit'
  351. : (isBitField
  352. ? (bitWidth === 1 ? `1bit/占${byteLength}B` : `${bitWidth}bit/占${byteLength}B`)
  353. : (textByteLength && textByteLength !== byteLength ? `${textByteLength}B/占${byteLength}B` : `${byteLength}B`)),
  354. byteStart: Math.max(0, Math.floor(Number(register.byteStart) || 0)),
  355. dataType,
  356. dataTypeIndex: getDataTypeIndex(dataType),
  357. dataTypeText: getRegisterValueTypeLabel(dataType),
  358. defaultValue,
  359. displayMetaText,
  360. displayValue: convertedDisplayValue,
  361. id: register.id || createId('gm-reg'),
  362. inputType: isTextRegister(dataType) ? 'text' : 'text',
  363. inputValue,
  364. isStructField: layout === GROUP_LAYOUT_STRUCT || !!register.isStructField,
  365. layout,
  366. isBitField,
  367. isPlaceholderByteField,
  368. isDirty: !!register.isDirty,
  369. maxValue: normalizeTextValue(register.maxValue),
  370. metaText,
  371. minValue: normalizeTextValue(register.minValue),
  372. name: register.name || `寄存器 ${index + 1}`,
  373. conversionFormula,
  374. conversionFormulaErrorText: conversionResult && conversionResult.errorText ? conversionResult.errorText : '',
  375. rawValue,
  376. rawValueText,
  377. rawBytes,
  378. rawWords,
  379. registerCount,
  380. byteOffset,
  381. registerType,
  382. showDataType: !isBitRegisterType(registerType),
  383. showRange: !isBitRegisterType(registerType) && !isBitField && supportsRange(dataType),
  384. showTextLength: !isBitRegisterType(registerType) && isTextRegister(dataType),
  385. showUnit: canShowUnit,
  386. textByteLength,
  387. unit: canShowUnit ? normalizeTextValue(register.unit).trim() : '',
  388. structByteLength: register.structByteLength,
  389. remark: register.remark || '',
  390. ...pickFields(register, SOURCE_REGISTER_FIELDS),
  391. sourceMetaText: createRegisterSourceMetaText(register)
  392. }
  393. }
  394. function normalizeGroup(group) {
  395. const registerType = getRegisterType(group.registerType || group.type || DEFAULT_REGISTER_TYPE)
  396. const startAddress = normalizeAddress(group.startAddress, 0)
  397. const maxQuantity = getMaxQuantity(registerType.key)
  398. const sourceRegisters = Array.isArray(group.registers) ? group.registers : []
  399. const codeInfoContext = group.codeInfoContext || {}
  400. const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  401. const byteAddressed = isByteAddressedGroup(group)
  402. const hasExplicitQuantity = group.quantity !== undefined && group.quantity !== null && group.quantity !== ''
  403. const quantity = hasExplicitQuantity
  404. ? clampInteger(group.quantity, 1, maxQuantity, 1)
  405. : clampInteger(sourceRegisters.length || 1, 1, maxQuantity, 1)
  406. const baseGroup = {
  407. deleteVisible: !!group.deleteVisible,
  408. expanded: group.expanded === true,
  409. id: group.id || createId('gm-group'),
  410. isStructLayout: layout === GROUP_LAYOUT_STRUCT,
  411. layout,
  412. name: String(group.name || group.groupName || '寄存器组').trim() || '寄存器组',
  413. quantity,
  414. registerType: registerType.key,
  415. startAddress,
  416. touchStartX: 0,
  417. codeInfoContext,
  418. ...pickFields(group, SOURCE_GROUP_FIELDS)
  419. }
  420. const registers = []
  421. let nextAddress = startAddress
  422. let nextByteOffset = 0
  423. for (let index = 0; index < quantity; index += 1) {
  424. const sourceRegister = sourceRegisters[index] || {}
  425. let normalizedSourceRegister = sourceRegister
  426. const dataType = normalizeRegisterDataType(sourceRegister, baseGroup.registerType)
  427. const textByteLength = isTextRegister(dataType)
  428. ? normalizeTextByteLength(sourceRegister.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  429. : ''
  430. const isBitRegister = isBitRegisterType(baseGroup.registerType)
  431. let address = startAddress + index
  432. let byteOffset = 0
  433. if (!isBitRegister) {
  434. const explicitByteStart = Number(sourceRegister.byteStart)
  435. const hasExplicitByteStart = layout === GROUP_LAYOUT_STRUCT && Number.isFinite(explicitByteStart)
  436. const byteLength = getRegisterByteLength(dataType, { ...sourceRegister, layout, textByteLength })
  437. if (layout !== GROUP_LAYOUT_STRUCT && !isByteRegister(dataType) && nextByteOffset % 2 !== 0) {
  438. nextByteOffset += 1
  439. }
  440. const currentByteStart = hasExplicitByteStart
  441. ? Math.max(0, Math.floor(explicitByteStart))
  442. : nextByteOffset
  443. address = byteAddressed ? startAddress + currentByteStart : startAddress + Math.floor(currentByteStart / 2)
  444. byteOffset = byteAddressed ? 0 : currentByteStart % 2
  445. normalizedSourceRegister = {
  446. ...sourceRegister,
  447. byteStart: currentByteStart
  448. }
  449. nextByteOffset = Math.max(nextByteOffset, currentByteStart + byteLength)
  450. }
  451. const register = normalizeRegister(normalizedSourceRegister, baseGroup, index, address, byteOffset)
  452. registers.push(register)
  453. if (isBitRegister) nextAddress += register.registerCount
  454. }
  455. const byteLength = isBitRegisterType(baseGroup.registerType)
  456. ? Math.max(1, nextAddress - startAddress)
  457. : Math.max(1, nextByteOffset)
  458. const paddedByteLength = isBitRegisterType(baseGroup.registerType)
  459. ? byteLength
  460. : (byteAddressed ? byteLength : alignEvenByteLength(byteLength))
  461. const wordQuantity = isBitRegisterType(baseGroup.registerType)
  462. ? Math.max(1, nextAddress - startAddress)
  463. : (byteAddressed ? Math.max(1, byteLength) : Math.max(1, paddedByteLength / 2))
  464. const addressSpan = byteAddressed ? Math.max(1, byteLength) : wordQuantity
  465. const addressOverflow = isAddressRangeOverflow(startAddress, addressSpan)
  466. const endAddress = startAddress + addressSpan - 1
  467. const startAddressText = `0x${padHex(startAddress)}`
  468. const endAddressText = addressOverflow ? `0x${padHex(MAX_MODBUS_ADDRESS)}+` : `0x${padHex(endAddress)}`
  469. const displayName = isStorageStructGroup(baseGroup)
  470. ? stripStorageAreaPrefix(baseGroup.name)
  471. : baseGroup.name
  472. const listMetaText = isStorageStructGroup(baseGroup)
  473. ? `${getStorageAreaText(baseGroup)} ${startAddressText}-${endAddressText} ${byteLength}B`
  474. : `${formatAddressRange(startAddress, addressSpan)} · ${quantity}/${wordQuantity}${createGroupSourceMetaText(baseGroup) ? ` · ${createGroupSourceMetaText(baseGroup)}` : ''}${addressOverflow ? ' · 地址超出 0xFFFF' : ''}`
  475. const detailMetaText = isStorageStructGroup(baseGroup)
  476. ? `${getStorageAreaText(baseGroup, true)} ${startAddressText} ${quantity}/${byteLength}B`
  477. : `${formatAddressRange(startAddress, addressSpan)} · ${quantity}/${wordQuantity}${createGroupSourceMetaText(baseGroup) ? ` · ${createGroupSourceMetaText(baseGroup)}` : ''}${addressOverflow ? ' · 地址超出 0xFFFF' : ''}`
  478. const sourceMetaText = createGroupSourceMetaText(baseGroup)
  479. return {
  480. ...baseGroup,
  481. addressRangeText: formatAddressRange(startAddress, addressSpan),
  482. addressOverflow,
  483. addressWarningText: addressOverflow ? '地址超出 0xFFFF' : '',
  484. detailMetaText,
  485. detailTitleText: displayName,
  486. displayName,
  487. endAddressText,
  488. functionCode: registerType.functionCode,
  489. isReadOnly: !registerType.writable,
  490. byteLength,
  491. listMetaText,
  492. maxQuantity,
  493. registerTypeIndex: getRegisterTypeIndex(registerType.key),
  494. registerTypeText: registerType.label,
  495. registers,
  496. paddedByteLength,
  497. sourceMetaText,
  498. startAddressText,
  499. wordQuantity,
  500. writable: registerType.writable
  501. }
  502. }
  503. function normalizeGroupConfig(config = {}) {
  504. const registerType = config.registerTypeIndex !== undefined && config.registerTypeIndex !== null
  505. ? (REGISTER_TYPE_OPTIONS[Number(config.registerTypeIndex)] || REGISTER_TYPE_OPTIONS[0])
  506. : getRegisterType(config.registerType || config.type || DEFAULT_REGISTER_TYPE)
  507. const maxQuantity = getMaxQuantity(registerType.key)
  508. return {
  509. layout: isStructLayout(config.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
  510. name: String(config.name || config.groupName || '寄存器组').trim() || '寄存器组',
  511. quantity: parseConfigQuantity(config.quantity, maxQuantity),
  512. registerType: registerType.key,
  513. startAddress: parseConfigAddress(config.startAddress)
  514. }
  515. }
  516. function getRegisterJsonValue(register) {
  517. if (register.inputValue !== undefined && register.inputValue !== null) {
  518. return normalizeTextValue(register.inputValue)
  519. }
  520. if (register.defaultValue !== undefined && register.defaultValue !== null) {
  521. return normalizeTextValue(register.defaultValue)
  522. }
  523. if (register.displayValue !== undefined && register.displayValue !== null && register.displayValue !== '--') {
  524. return normalizeTextValue(register.displayValue)
  525. }
  526. return ''
  527. }
  528. function normalizeImportedRegisterDataType(register) {
  529. const dataType = register.dataType || register.type || DEFAULT_DATA_TYPE
  530. return getDataType(dataType).key
  531. }
  532. function cloneImportedGroup(group) {
  533. return {
  534. layout: isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
  535. name: group.name,
  536. quantity: group.quantity,
  537. registerType: group.registerType || group.type || DEFAULT_REGISTER_TYPE,
  538. registers: (Array.isArray(group.registers) ? group.registers : []).map((register) => ({
  539. conversionFormula: register.conversionFormula,
  540. dataType: normalizeImportedRegisterDataType(register),
  541. defaultValue: register.defaultValue,
  542. inputValue: register.inputValue,
  543. maxValue: register.maxValue,
  544. minValue: register.minValue,
  545. name: register.name,
  546. isStructField: !!register.isStructField,
  547. textByteLength: register.textByteLength,
  548. remark: register.remark,
  549. unit: register.unit,
  550. value: register.value,
  551. ...pickFields(register, STRUCT_REGISTER_FIELDS),
  552. ...pickFields(register, SOURCE_REGISTER_FIELDS)
  553. })),
  554. startAddress: group.startAddress,
  555. ...pickFields(group, SOURCE_GROUP_FIELDS)
  556. }
  557. }
  558. module.exports = {
  559. DATA_TYPE_OPTIONS,
  560. DEFAULT_DATA_TYPE,
  561. DEFAULT_REGISTER_TYPE,
  562. GROUP_LAYOUT_REGISTER,
  563. GROUP_LAYOUT_STRUCT,
  564. MAX_MODBUS_ADDRESS,
  565. REGISTER_TYPE_OPTIONS,
  566. cloneImportedGroup,
  567. decodeRegisterFromByteCache,
  568. decodeRegisterFromWordCache,
  569. decodeRegisterValue,
  570. formatCoilDisplayValue,
  571. formatRegisterValue,
  572. getDataType,
  573. getRegisterEncodedBytes,
  574. getRegisterEncodedWords,
  575. getGroupEncodedBytes,
  576. getGroupEncodedWords,
  577. getRegisterWordCount,
  578. getRegisterJsonValue,
  579. getRegisterBytesFromByteCache,
  580. getRegisterWordsFromByteCache,
  581. getRegisterWordsFromWordCache,
  582. getRegisterWriteValueText,
  583. isAddressRangeOverflow,
  584. isBitRegisterType,
  585. isByteRegister,
  586. isTextRegister,
  587. normalizeGroup,
  588. normalizeGroupConfig,
  589. normalizeStorageCodeInfoCard,
  590. normalizeRegister,
  591. parseCoilValue,
  592. registerTypeIsBit,
  593. splitWordSpans,
  594. validateRegisterValue
  595. }