model.js 35 KB

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