1
0

model.js 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398
  1. const {
  2. formatFixedValue
  3. } = require('../../utils/number-format.js')
  4. const {
  5. clampInteger,
  6. createId,
  7. normalizeTextValue,
  8. padHex
  9. } = require('../../utils/base-utils.js')
  10. const {
  11. bytesToWords,
  12. getByteFromWord,
  13. trimTrailingNullBytes,
  14. wordsToBytes
  15. } = require('../../utils/binary-utils.js')
  16. const MAX_MODBUS_ADDRESS = 0xFFFF
  17. const MAX_GENERIC_MODBUS_ITEMS = 256
  18. const DEFAULT_TEXT_BYTE_LENGTH = 32
  19. const MAX_TEXT_BYTE_LENGTH = 32
  20. const REGISTER_TYPE_OPTIONS = [
  21. {
  22. functionCode: 0x03,
  23. key: 'holding',
  24. label: '保持寄存器',
  25. writable: true
  26. },
  27. {
  28. functionCode: 0x01,
  29. key: 'coil',
  30. label: '线圈',
  31. writable: true
  32. },
  33. {
  34. functionCode: 0x02,
  35. key: 'discrete',
  36. label: '离散输入状态',
  37. writable: false
  38. },
  39. {
  40. functionCode: 0x04,
  41. key: 'input',
  42. label: '输入寄存器',
  43. writable: false
  44. }
  45. ]
  46. const DATA_TYPE_OPTIONS = [
  47. {
  48. byteLength: 1,
  49. key: 'int8_t',
  50. label: 'int8_t',
  51. kind: 'number',
  52. wordCount: 1
  53. },
  54. {
  55. byteLength: 1,
  56. key: 'uint8_t',
  57. label: 'uint8_t',
  58. kind: 'number',
  59. wordCount: 1
  60. },
  61. {
  62. byteLength: 2,
  63. key: 'int16_t',
  64. label: 'int16_t',
  65. kind: 'number',
  66. wordCount: 1
  67. },
  68. {
  69. byteLength: 2,
  70. key: 'uint16_t',
  71. label: 'uint16_t',
  72. kind: 'number',
  73. wordCount: 1
  74. },
  75. {
  76. byteLength: 4,
  77. key: 'int32_t',
  78. label: 'int32_t',
  79. kind: 'number',
  80. wordCount: 2
  81. },
  82. {
  83. byteLength: 4,
  84. key: 'uint32_t',
  85. label: 'uint32_t',
  86. kind: 'number',
  87. wordCount: 2
  88. },
  89. {
  90. byteLength: 4,
  91. key: 'float',
  92. label: 'float',
  93. kind: 'number',
  94. wordCount: 2
  95. },
  96. {
  97. byteLength: 32,
  98. key: 'utf8',
  99. label: 'UTF-8',
  100. kind: 'text',
  101. maxByteLength: MAX_TEXT_BYTE_LENGTH,
  102. wordCount: 16
  103. },
  104. {
  105. byteLength: 32,
  106. key: 'ascii',
  107. label: 'ASCII',
  108. kind: 'text',
  109. maxByteLength: MAX_TEXT_BYTE_LENGTH,
  110. wordCount: 16
  111. },
  112. {
  113. byteLength: 2,
  114. key: 'hex',
  115. label: 'HEX',
  116. kind: 'hex',
  117. wordCount: 1
  118. }
  119. ]
  120. const DEFAULT_REGISTER_TYPE = REGISTER_TYPE_OPTIONS[0].key
  121. const DEFAULT_DATA_TYPE = 'uint16_t'
  122. const GROUP_LAYOUT_REGISTER = 'register'
  123. const GROUP_LAYOUT_STRUCT = 'struct'
  124. const BYTE_ADDRESS_MEMORY_AREAS = ['BIT', 'CODE', 'DATA', 'IDATA', 'XDATA']
  125. const SOURCE_REGISTER_FIELDS = [
  126. 'sourceAddress',
  127. 'sourceAddressText',
  128. 'sourceByteLength',
  129. 'sourceBitOffset',
  130. 'sourceBitWidth',
  131. 'sourceMemoryArea',
  132. 'sourceMemoryClass',
  133. 'sourceSymbolName',
  134. 'sourceSymbolType'
  135. ]
  136. const STRUCT_REGISTER_FIELDS = [
  137. 'bitOffset',
  138. 'bitWidth',
  139. 'byteStart',
  140. 'isBitField',
  141. 'structByteLength'
  142. ]
  143. const SOURCE_GROUP_FIELDS = [
  144. 'addressUnit',
  145. 'sourceAddress',
  146. 'sourceAddressText',
  147. 'sourceByteLength',
  148. 'sourceMemoryArea',
  149. 'sourceMemoryClass',
  150. 'sourceSegment',
  151. 'sourceSegmentModule',
  152. 'sourceSymbolName'
  153. ]
  154. function normalizeAddress(value, fallback = 0) {
  155. if (typeof value === 'number') {
  156. return Number.isFinite(value) ? clampInteger(value, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  157. }
  158. const text = String(value === undefined || value === null ? '' : value).trim()
  159. if (!text) return fallback
  160. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  161. if (/^[0-9A-F]+$/i.test(hexText)) {
  162. const parsedHex = parseInt(hexText, 16)
  163. return Number.isFinite(parsedHex) ? clampInteger(parsedHex, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  164. }
  165. const numberValue = Number(text)
  166. return Number.isFinite(numberValue) ? clampInteger(numberValue, 0, MAX_MODBUS_ADDRESS, fallback) : fallback
  167. }
  168. function parseConfigAddress(value) {
  169. if (typeof value === 'number') {
  170. return clampInteger(value, 0, MAX_MODBUS_ADDRESS, 0)
  171. }
  172. const text = String(value === undefined || value === null ? '' : value).trim()
  173. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  174. if (!/^[0-9A-F]{1,4}$/i.test(hexText)) {
  175. throw new Error('寄存器起始地址无效')
  176. }
  177. return parseInt(hexText, 16)
  178. }
  179. function parseConfigQuantity(value, maxQuantity) {
  180. const text = String(value === undefined || value === null ? '' : value).trim()
  181. const quantity = Number(text)
  182. if (!Number.isInteger(quantity) || quantity < 1 || quantity > maxQuantity) {
  183. throw new Error(`寄存器数量需为 1 - ${maxQuantity}`)
  184. }
  185. return quantity
  186. }
  187. function getRegisterType(typeKey) {
  188. return REGISTER_TYPE_OPTIONS.find((item) => item.key === typeKey) || REGISTER_TYPE_OPTIONS[0]
  189. }
  190. function getRegisterTypeIndex(typeKey) {
  191. return Math.max(0, REGISTER_TYPE_OPTIONS.findIndex((item) => item.key === getRegisterType(typeKey).key))
  192. }
  193. function getDataType(dataType) {
  194. return DATA_TYPE_OPTIONS.find((item) => item.key === dataType)
  195. || DATA_TYPE_OPTIONS.find((item) => item.key === DEFAULT_DATA_TYPE)
  196. || DATA_TYPE_OPTIONS[0]
  197. }
  198. function getDataTypeIndex(dataType) {
  199. return Math.max(0, DATA_TYPE_OPTIONS.findIndex((item) => item.key === getDataType(dataType).key))
  200. }
  201. function normalizeTextByteLength(value, fallback = DEFAULT_TEXT_BYTE_LENGTH) {
  202. const numberValue = Number(value)
  203. const rounded = Number.isFinite(numberValue) ? Math.round(numberValue) : fallback
  204. return Math.min(Math.max(rounded, 1), MAX_TEXT_BYTE_LENGTH)
  205. }
  206. function alignEvenByteLength(byteLength) {
  207. const length = Math.max(1, Math.round(Number(byteLength) || 1))
  208. return length % 2 === 0 ? length : length + 1
  209. }
  210. function getRegisterTextByteLength(register = {}) {
  211. return normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  212. }
  213. function isStructLayout(layout) {
  214. return layout === GROUP_LAYOUT_STRUCT
  215. }
  216. function isBitFieldRegister(register = {}) {
  217. return !!register.isBitField
  218. }
  219. function normalizeBitOffset(value) {
  220. const numberValue = Math.floor(Number(value) || 0)
  221. return Math.min(Math.max(numberValue, 0), 7)
  222. }
  223. function normalizeBitWidth(value) {
  224. const numberValue = Math.round(Number(value) || 1)
  225. return Math.min(Math.max(numberValue, 1), 32)
  226. }
  227. function getBitFieldByteLength(register = {}) {
  228. const bitOffset = normalizeBitOffset(register.bitOffset)
  229. const bitWidth = normalizeBitWidth(register.bitWidth)
  230. return Math.max(1, Math.ceil((bitOffset + bitWidth) / 8))
  231. }
  232. function getBitFieldMaxValue(register = {}) {
  233. const bitWidth = normalizeBitWidth(register.bitWidth)
  234. return bitWidth >= 32 ? 0xFFFFFFFF : Math.pow(2, bitWidth) - 1
  235. }
  236. function getRegisterByteLength(dataType, register = {}) {
  237. if (isBitFieldRegister(register)) return getBitFieldByteLength(register)
  238. const type = getDataType(dataType)
  239. if (type.kind === 'text') {
  240. const byteLength = getRegisterTextByteLength(register)
  241. return isStructLayout(register.layout) ? byteLength : alignEvenByteLength(byteLength)
  242. }
  243. return type.byteLength || ((type.wordCount || 1) * 2)
  244. }
  245. function getRegisterWordCount(dataType, register = {}) {
  246. return Math.max(1, Math.ceil(getRegisterByteLength(dataType, register) / 2))
  247. }
  248. function getByteSpanWordCount(byteOffset, byteLength) {
  249. return Math.max(1, Math.ceil((Math.max(0, Number(byteOffset) || 0) + Math.max(1, Number(byteLength) || 1)) / 2))
  250. }
  251. function getRegisterWordCountAtOffset(dataType, byteOffset, register = {}) {
  252. const byteLength = getRegisterByteLength(dataType, register)
  253. return getByteSpanWordCount(byteOffset, byteLength)
  254. }
  255. function getEncodeByteLimit(register) {
  256. return isTextRegister(register.dataType) ? getRegisterTextByteLength(register) : getRegisterByteLength(register.dataType, register)
  257. }
  258. function isTextRegister(dataType) {
  259. return getDataType(dataType).kind === 'text'
  260. }
  261. function isByteRegister(dataType) {
  262. const key = getDataType(dataType).key
  263. return key === 'int8_t' || key === 'uint8_t'
  264. }
  265. function isBitRegisterType(registerType) {
  266. return registerType === 'coil' || registerType === 'discrete'
  267. }
  268. function isHexRegister(dataType) {
  269. return getDataType(dataType).key === 'hex'
  270. }
  271. function isNumericRegister(dataType) {
  272. return getDataType(dataType).kind === 'number'
  273. }
  274. function supportsRange(dataType) {
  275. return isNumericRegister(dataType) || isHexRegister(dataType)
  276. }
  277. function supportsUnit(dataType) {
  278. return isNumericRegister(dataType)
  279. }
  280. function padWordHex(value) {
  281. return Number(value || 0).toString(16).toUpperCase().padStart(4, '0')
  282. }
  283. function formatRawWordText(words = []) {
  284. if (!Array.isArray(words) || !words.length) return '--'
  285. return words.map((word) => `0x${padWordHex(word)}`).join(' ')
  286. }
  287. function formatRawByteText(bytes = []) {
  288. if (!Array.isArray(bytes) || !bytes.length) return '--'
  289. return bytes.map((byte) => `0x${(Number(byte) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}`).join(' ')
  290. }
  291. function formatAddressRange(startAddress, wordCount) {
  292. const address = normalizeAddress(startAddress, 0)
  293. const count = Math.max(1, Number(wordCount) || 1)
  294. const endAddress = address + count - 1
  295. const safeEndAddress = Math.min(endAddress, MAX_MODBUS_ADDRESS)
  296. const overflowText = endAddress > MAX_MODBUS_ADDRESS ? '+' : ''
  297. if (count <= 1) return `0x${padWordHex(address)}`
  298. return `0x${padWordHex(address)}-0x${padWordHex(safeEndAddress)}${overflowText}`
  299. }
  300. function isByteAddressedGroup(group = {}) {
  301. const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase()
  302. return group.addressUnit === 'byte'
  303. || group.addressUnit === 'bytes'
  304. || BYTE_ADDRESS_MEMORY_AREAS.indexOf(memoryArea) >= 0
  305. }
  306. function formatRegisterAddressText(address, byteOffset, byteLength, registerType) {
  307. if (isBitRegisterType(registerType)) return `0x${padHex(address)}`
  308. if (byteLength === 1) return `0x${padHex(address)}${byteOffset === 0 ? 'H' : 'L'}`
  309. return `0x${padHex(address)}`
  310. }
  311. function formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth) {
  312. const byteText = formatRegisterAddressText(address, byteOffset, 1, DEFAULT_REGISTER_TYPE)
  313. const startBit = normalizeBitOffset(bitOffset)
  314. const endBit = startBit + normalizeBitWidth(bitWidth) - 1
  315. return endBit === startBit
  316. ? `${byteText}.b${startBit}`
  317. : `${byteText}.b${startBit}..${endBit}`
  318. }
  319. function isAddressRangeOverflow(startAddress, wordCount) {
  320. const address = normalizeAddress(startAddress, 0)
  321. const count = Math.max(1, Number(wordCount) || 1)
  322. return address + count - 1 > MAX_MODBUS_ADDRESS
  323. }
  324. function encodeAsciiBytes(text, byteLimit = 32) {
  325. const bytes = []
  326. const stringValue = normalizeTextValue(text)
  327. for (let index = 0; index < stringValue.length; index += 1) {
  328. const code = stringValue.charCodeAt(index)
  329. if (code > 0x7F) {
  330. throw new Error('ASCII 文本只能包含 0x00 - 0x7F 字符')
  331. }
  332. bytes.push(code)
  333. if (bytes.length > byteLimit) break
  334. }
  335. if (bytes.length > byteLimit) {
  336. throw new Error(`长文本最长 ${byteLimit} 字节`)
  337. }
  338. return bytes
  339. }
  340. function encodeUtf8Bytes(text, byteLimit = 32) {
  341. const bytes = []
  342. const encoded = encodeURIComponent(normalizeTextValue(text))
  343. for (let index = 0; index < encoded.length; index += 1) {
  344. const char = encoded[index]
  345. if (char === '%') {
  346. const byte = parseInt(encoded.slice(index + 1, index + 3), 16)
  347. if (!Number.isFinite(byte)) break
  348. bytes.push(byte & 0xFF)
  349. index += 2
  350. } else {
  351. bytes.push(char.charCodeAt(0) & 0xFF)
  352. }
  353. if (bytes.length > byteLimit) break
  354. }
  355. if (bytes.length > byteLimit) {
  356. throw new Error(`长文本最长 ${byteLimit} 字节`)
  357. }
  358. return bytes
  359. }
  360. function decodeAsciiBytes(bytes = []) {
  361. return String.fromCharCode.apply(null, trimTrailingNullBytes(bytes).map((byte) => byte & 0xFF))
  362. }
  363. function decodeUtf8Bytes(bytes = []) {
  364. const trimmed = trimTrailingNullBytes(bytes)
  365. if (!trimmed.length) return ''
  366. let encoded = ''
  367. trimmed.forEach((byte) => {
  368. encoded += `%${(byte & 0xFF).toString(16).padStart(2, '0').toUpperCase()}`
  369. })
  370. try {
  371. return decodeURIComponent(encoded)
  372. } catch (error) {
  373. return decodeAsciiBytes(trimmed)
  374. }
  375. }
  376. function encodeTextBytes(text, dataType, byteLimit = MAX_TEXT_BYTE_LENGTH) {
  377. const normalizedType = getDataType(dataType).key
  378. if (normalizedType === 'ascii') return encodeAsciiBytes(text, byteLimit)
  379. return encodeUtf8Bytes(text, byteLimit)
  380. }
  381. function decodeTextBytes(bytes, dataType) {
  382. const normalizedType = getDataType(dataType).key
  383. return normalizedType === 'ascii'
  384. ? decodeAsciiBytes(bytes)
  385. : decodeUtf8Bytes(bytes)
  386. }
  387. function formatIntegerValue(value, dataType) {
  388. const type = getDataType(dataType).key
  389. const numberValue = Number(value)
  390. if (!Number.isFinite(numberValue)) return '--'
  391. if (type === 'int8_t') return String(((Math.round(numberValue) << 24) >> 24))
  392. if (type === 'uint8_t') return String(Math.round(numberValue) & 0xFF)
  393. if (type === 'int16_t') return String(((Math.round(numberValue) << 16) >> 16))
  394. if (type === 'uint16_t') return String(Math.round(numberValue) & 0xFFFF)
  395. if (type === 'int32_t') return String((Math.round(numberValue) | 0))
  396. if (type === 'uint32_t') return String(Math.round(numberValue) >>> 0)
  397. return String(Math.round(numberValue))
  398. }
  399. function formatHexValue(value) {
  400. const numberValue = Number(value)
  401. if (!Number.isFinite(numberValue)) return '--'
  402. return `0x${padWordHex(Math.round(numberValue) & 0xFFFF)}`
  403. }
  404. function formatFloatValue(value) {
  405. return formatFixedValue(value, 6).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
  406. }
  407. function parseIntegerText(value) {
  408. const text = String(value === undefined || value === null ? '' : value).trim()
  409. if (!text) return null
  410. const isHex = /^[-+]?0x[0-9a-f]+$/i.test(text) || /^0x[0-9a-f]+$/i.test(text)
  411. const parsed = isHex ? parseInt(text, 16) : Number(text)
  412. return Number.isFinite(parsed) ? parsed : null
  413. }
  414. function parseHexText(value) {
  415. const text = String(value === undefined || value === null ? '' : value).trim()
  416. if (!text) return null
  417. const hexText = text.toUpperCase().startsWith('0X') ? text.slice(2) : text
  418. if (/^[0-9A-F]{1,4}$/i.test(hexText)) {
  419. const parsedHex = parseInt(hexText, 16)
  420. return Number.isFinite(parsedHex) ? parsedHex : null
  421. }
  422. return null
  423. }
  424. function getRegisterValueTypeLabel(dataType) {
  425. return getDataType(dataType).label
  426. }
  427. function getMaxQuantity() {
  428. return MAX_GENERIC_MODBUS_ITEMS
  429. }
  430. function parseCoilValue(value) {
  431. const text = String(value === undefined || value === null ? '' : value).trim()
  432. if (!text || text === '--') return null
  433. if (['1', 'true', 'TRUE', 'on', 'ON', '开'].includes(text)) return 1
  434. if (['0', 'false', 'FALSE', 'off', 'OFF', '关'].includes(text)) return 0
  435. const coilValue = Number(text)
  436. return Number.isFinite(coilValue) ? (coilValue ? 1 : 0) : null
  437. }
  438. function getNumericRange(dataType) {
  439. const type = getDataType(dataType).key
  440. if (type === 'int8_t') return { max: 127, min: -128 }
  441. if (type === 'uint8_t') return { max: 0xFF, min: 0 }
  442. if (type === 'int16_t') return { max: 32767, min: -32768 }
  443. if (type === 'uint16_t') return { max: 0xFFFF, min: 0 }
  444. if (type === 'int32_t') return { max: 2147483647, min: -2147483648 }
  445. if (type === 'uint32_t') return { max: 0xFFFFFFFF, min: 0 }
  446. if (type === 'hex') return { max: 0xFFFF, min: 0 }
  447. return { max: Number.POSITIVE_INFINITY, min: Number.NEGATIVE_INFINITY }
  448. }
  449. function parseNumberText(value, dataType) {
  450. const text = String(value === undefined || value === null ? '' : value).trim()
  451. if (!text || text === '--') return null
  452. if (getDataType(dataType).key === 'float') {
  453. const parsed = Number(text)
  454. return Number.isFinite(parsed) ? parsed : null
  455. }
  456. if (isHexRegister(dataType)) return parseHexText(text)
  457. return parseIntegerText(text)
  458. }
  459. function parseRangeBoundary(value, dataType, label) {
  460. const text = String(value === undefined || value === null ? '' : value).trim()
  461. if (!text) return null
  462. const parsed = parseNumberText(text, dataType)
  463. if (parsed === null) {
  464. throw new Error(`${label}无效`)
  465. }
  466. return parsed
  467. }
  468. function validateNumericValue(register, value) {
  469. const dataType = getDataType(register.dataType).key
  470. const range = getNumericRange(dataType)
  471. const numberValue = Number(value)
  472. if (!Number.isFinite(numberValue)) return false
  473. if (dataType !== 'float' && Math.round(numberValue) !== numberValue) {
  474. throw new Error(`${register.name || '寄存器'} 需要整数`)
  475. }
  476. if (numberValue < range.min || numberValue > range.max) {
  477. throw new Error(`${register.name || '寄存器'} 超出 ${dataType} 范围`)
  478. }
  479. const minValue = parseRangeBoundary(register.minValue, dataType, `${register.name || '寄存器'} 最小值`)
  480. const maxValue = parseRangeBoundary(register.maxValue, dataType, `${register.name || '寄存器'} 最大值`)
  481. if (minValue !== null && numberValue < minValue) {
  482. throw new Error(`${register.name || '寄存器'} 小于限制最小值`)
  483. }
  484. if (maxValue !== null && numberValue > maxValue) {
  485. throw new Error(`${register.name || '寄存器'} 大于限制最大值`)
  486. }
  487. return true
  488. }
  489. function floatToWords(value) {
  490. const buffer = new ArrayBuffer(4)
  491. const view = new DataView(buffer)
  492. view.setFloat32(0, Number(value), false)
  493. return [view.getUint16(0, false), view.getUint16(2, false)]
  494. }
  495. function wordsToFloat(words) {
  496. if (!Array.isArray(words) || words.length < 2) return null
  497. const buffer = new ArrayBuffer(4)
  498. const view = new DataView(buffer)
  499. view.setUint16(0, Number(words[0]) & 0xFFFF, false)
  500. view.setUint16(2, Number(words[1]) & 0xFFFF, false)
  501. return view.getFloat32(0, false)
  502. }
  503. function floatToBytes(value) {
  504. const buffer = new ArrayBuffer(4)
  505. const view = new DataView(buffer)
  506. view.setFloat32(0, Number(value), false)
  507. return [
  508. view.getUint8(0),
  509. view.getUint8(1),
  510. view.getUint8(2),
  511. view.getUint8(3)
  512. ]
  513. }
  514. function bytesToFloatValue(bytes) {
  515. if (!Array.isArray(bytes) || bytes.length < 4) return null
  516. const buffer = new ArrayBuffer(4)
  517. const view = new DataView(buffer)
  518. for (let index = 0; index < 4; index += 1) {
  519. view.setUint8(index, Number(bytes[index]) & 0xFF)
  520. }
  521. return view.getFloat32(0, false)
  522. }
  523. function unsignedIntegerToBytes(value, byteLength) {
  524. let numberValue = Math.round(Number(value) || 0)
  525. const bytes = []
  526. if (numberValue < 0) {
  527. numberValue += Math.pow(2, byteLength * 8)
  528. }
  529. for (let index = byteLength - 1; index >= 0; index -= 1) {
  530. bytes[index] = numberValue & 0xFF
  531. numberValue = Math.floor(numberValue / 0x100)
  532. }
  533. return bytes
  534. }
  535. function bytesToUnsignedInteger(bytes) {
  536. return bytes.reduce((value, byte) => ((value * 0x100) + (Number(byte) & 0xFF)), 0)
  537. }
  538. function bytesToSignedInteger(bytes) {
  539. const unsignedValue = bytesToUnsignedInteger(bytes)
  540. const signLimit = Math.pow(2, bytes.length * 8 - 1)
  541. const fullRange = Math.pow(2, bytes.length * 8)
  542. return unsignedValue >= signLimit ? unsignedValue - fullRange : unsignedValue
  543. }
  544. function parseBitFieldValue(register, valueText) {
  545. const text = normalizeTextValue(valueText).trim()
  546. const bitWidth = normalizeBitWidth(register.bitWidth)
  547. let parsed = parseNumberText(text, 'uint32_t')
  548. const maxValue = getBitFieldMaxValue(register)
  549. if (parsed === null && bitWidth === 1) parsed = parseCoilValue(text)
  550. if (parsed === null) return null
  551. if (Math.round(parsed) !== parsed || parsed < 0 || parsed > maxValue) {
  552. throw new Error(`${register.name || '位域'} 超出 0 - ${maxValue} 范围`)
  553. }
  554. return Math.round(parsed)
  555. }
  556. function decodeBitFieldBytes(register, bytes = []) {
  557. const bitOffset = normalizeBitOffset(register.bitOffset)
  558. const bitWidth = normalizeBitWidth(register.bitWidth)
  559. let byteIndex = 0
  560. let currentBitOffset = bitOffset
  561. let multiplier = 1
  562. let remaining = bitWidth
  563. let value = 0
  564. while (remaining > 0 && byteIndex < bytes.length) {
  565. const take = Math.min(8 - currentBitOffset, remaining)
  566. const mask = (1 << take) - 1
  567. const part = ((Number(bytes[byteIndex]) & 0xFF) >> currentBitOffset) & mask
  568. value += part * multiplier
  569. multiplier *= Math.pow(2, take)
  570. remaining -= take
  571. byteIndex += 1
  572. currentBitOffset = 0
  573. }
  574. return remaining > 0 ? null : value
  575. }
  576. function encodeBitFieldIntoBytes(register, bytes, byteStart = 0) {
  577. const valueText = normalizeTextValue(register.inputValue)
  578. let value = parseBitFieldValue(register, valueText)
  579. const bitOffset = normalizeBitOffset(register.bitOffset)
  580. const bitWidth = normalizeBitWidth(register.bitWidth)
  581. let byteIndex = Math.max(0, Math.floor(Number(byteStart) || 0))
  582. let currentBitOffset = bitOffset
  583. let remaining = bitWidth
  584. if (value === null) return null
  585. while (remaining > 0) {
  586. const take = Math.min(8 - currentBitOffset, remaining)
  587. const mask = (1 << take) - 1
  588. const shiftedMask = (mask << currentBitOffset) & 0xFF
  589. const part = value & mask
  590. if (byteIndex >= bytes.length) return null
  591. bytes[byteIndex] = ((Number(bytes[byteIndex]) & 0xFF) & (~shiftedMask & 0xFF))
  592. | ((part << currentBitOffset) & shiftedMask)
  593. value = Math.floor(value / Math.pow(2, take))
  594. remaining -= take
  595. byteIndex += 1
  596. currentBitOffset = 0
  597. }
  598. return bytes
  599. }
  600. function encodeBitFieldBytes(register) {
  601. const bytes = Array.from({ length: getBitFieldByteLength(register) }, () => 0)
  602. return encodeBitFieldIntoBytes(register, bytes, 0)
  603. }
  604. function getRegisterDataBytes(register, words) {
  605. const dataType = getDataType(register.dataType).key
  606. const byteLength = getRegisterByteLength(dataType, register)
  607. const byteOffset = Math.max(0, Math.floor(Number(register.byteOffset) || 0))
  608. const sourceBytes = wordsToBytes(words, Math.max(0, (Array.isArray(words) ? words.length : 0) * 2))
  609. return sourceBytes.slice(byteOffset, byteOffset + byteLength)
  610. }
  611. function encodeRegisterBytes(register) {
  612. const dataType = getDataType(register.dataType).key
  613. const valueText = normalizeTextValue(register.inputValue)
  614. const byteLength = getRegisterByteLength(dataType, register)
  615. if (isBitFieldRegister(register)) {
  616. return encodeBitFieldBytes(register)
  617. }
  618. if (isTextRegister(dataType)) {
  619. const byteLimit = getEncodeByteLimit(register)
  620. const bytes = encodeTextBytes(valueText, dataType, byteLimit)
  621. const paddedBytes = bytes.slice()
  622. while (paddedBytes.length < byteLength) {
  623. paddedBytes.push(0)
  624. }
  625. return paddedBytes.slice(0, byteLength)
  626. }
  627. const numberValue = parseNumberText(valueText, dataType)
  628. if (numberValue === null) return null
  629. validateNumericValue(register, numberValue)
  630. if (dataType === 'float') return floatToBytes(numberValue)
  631. const rounded = Math.round(numberValue)
  632. if (dataType === 'int8_t' || dataType === 'uint8_t') return [rounded & 0xFF]
  633. if (dataType === 'int16_t' || dataType === 'uint16_t' || dataType === 'hex') {
  634. return unsignedIntegerToBytes(rounded, 2)
  635. }
  636. if (dataType === 'int32_t' || dataType === 'uint32_t') {
  637. return unsignedIntegerToBytes(rounded, 4)
  638. }
  639. return unsignedIntegerToBytes(rounded, byteLength)
  640. }
  641. function encodeRegisterWords(register) {
  642. const dataType = getDataType(register.dataType).key
  643. const bytes = encodeRegisterBytes(register)
  644. if (!Array.isArray(bytes)) return null
  645. if (isByteRegister(dataType)) return [bytes[0] & 0xFF]
  646. return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
  647. }
  648. function decodeRegisterValue(register, words) {
  649. const dataType = getDataType(register.dataType).key
  650. if (!Array.isArray(words) || words.length < getRegisterWordCountAtOffset(dataType, register.byteOffset || 0, register)) return null
  651. const bytes = getRegisterDataBytes(register, words)
  652. const byteLength = getRegisterByteLength(dataType, register)
  653. if (bytes.length < byteLength) return null
  654. if (isBitFieldRegister(register)) {
  655. return decodeBitFieldBytes(register, bytes)
  656. }
  657. if (isTextRegister(dataType)) {
  658. return decodeTextBytes(bytes.slice(0, getEncodeByteLimit(register)), dataType)
  659. }
  660. if (dataType === 'float') {
  661. return bytesToFloatValue(bytes)
  662. }
  663. if (dataType === 'int8_t') {
  664. const byteValue = bytes[0] & 0xFF
  665. return byteValue & 0x80 ? byteValue - 0x100 : byteValue
  666. }
  667. if (dataType === 'uint8_t') {
  668. return bytes[0] & 0xFF
  669. }
  670. if (dataType === 'int16_t') {
  671. return bytesToSignedInteger(bytes.slice(0, 2))
  672. }
  673. if (dataType === 'uint16_t') {
  674. return bytesToUnsignedInteger(bytes.slice(0, 2))
  675. }
  676. if (dataType === 'hex') {
  677. return bytesToUnsignedInteger(bytes.slice(0, 2))
  678. }
  679. if (dataType === 'int32_t') {
  680. return bytesToSignedInteger(bytes.slice(0, 4))
  681. }
  682. return bytesToUnsignedInteger(bytes.slice(0, 4))
  683. }
  684. function formatRegisterValue(register, rawValue) {
  685. if (rawValue === null || rawValue === undefined) return '--'
  686. const dataType = getDataType(register.dataType).key
  687. if (isTextRegister(dataType)) return normalizeTextValue(rawValue)
  688. if (dataType === 'hex') return formatHexValue(rawValue)
  689. if (dataType === 'float') return formatFloatValue(rawValue)
  690. return formatIntegerValue(rawValue, dataType)
  691. }
  692. function formatCoilDisplayValue(value) {
  693. return Number(value) ? '1' : '0'
  694. }
  695. function getRegisterSavedValue(register) {
  696. if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
  697. if (register.value !== undefined && register.value !== null) return normalizeTextValue(register.value)
  698. return null
  699. }
  700. function normalizeRegisterDataType(register, registerType) {
  701. if (isBitRegisterType(registerType)) return DEFAULT_DATA_TYPE
  702. return getDataType(register.dataType || register.type || DEFAULT_DATA_TYPE).key
  703. }
  704. function pickFields(source, fields) {
  705. return fields.reduce((result, field) => {
  706. if (source && source[field] !== undefined && source[field] !== null && source[field] !== '') {
  707. result[field] = source[field]
  708. }
  709. return result
  710. }, {})
  711. }
  712. function createRegisterSourceMetaText(register) {
  713. const bitText = isBitFieldRegister(register)
  714. ? `bit${normalizeBitOffset(register.bitOffset)}:${normalizeBitWidth(register.bitWidth)}`
  715. : ''
  716. const parts = [
  717. register.sourceMemoryArea,
  718. register.sourceAddressText,
  719. bitText,
  720. register.sourceSymbolType && register.sourceSymbolType !== '---' ? register.sourceSymbolType : ''
  721. ].filter(Boolean)
  722. return parts.join(' · ')
  723. }
  724. function createGroupSourceMetaText(group) {
  725. const parts = [
  726. group.sourceMemoryArea,
  727. group.sourceAddressText,
  728. group.sourceSymbolName,
  729. group.sourceSegmentModule
  730. ].filter(Boolean)
  731. return parts.join(' · ')
  732. }
  733. function normalizeRegister(register, group, index, address, byteOffset = 0) {
  734. const registerType = getRegisterType(group.registerType).key
  735. const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  736. const byteAddressed = isByteAddressedGroup(group)
  737. const dataType = normalizeRegisterDataType(register, registerType)
  738. const bitOffset = normalizeBitOffset(register.bitOffset)
  739. const bitWidth = normalizeBitWidth(register.bitWidth)
  740. const isBitField = !isBitRegisterType(registerType) && isBitFieldRegister(register)
  741. const textByteLength = isTextRegister(dataType)
  742. ? normalizeTextByteLength(register.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  743. : ''
  744. const defaultValue = normalizeTextValue(register.defaultValue)
  745. const savedValue = getRegisterSavedValue(register)
  746. const inputValue = savedValue === null ? defaultValue : savedValue
  747. const rawValue = register.rawValue === undefined ? null : register.rawValue
  748. const byteLength = isBitRegisterType(registerType)
  749. ? 1
  750. : getRegisterByteLength(dataType, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
  751. const registerCount = isBitRegisterType(registerType)
  752. ? 1
  753. : getRegisterWordCountAtOffset(dataType, byteOffset, { ...register, bitOffset, bitWidth, isBitField, layout, textByteLength })
  754. const canShowUnit = !isBitRegisterType(registerType) && !isBitField && supportsUnit(dataType)
  755. const rawWords = Array.isArray(register.rawWords)
  756. ? register.rawWords.slice(0, registerCount).map((word) => Number(word) & 0xFFFF)
  757. : []
  758. const rawBytes = Array.isArray(register.rawBytes)
  759. ? register.rawBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF)
  760. : []
  761. const rawValueText = rawValue === null
  762. ? '--'
  763. : (isBitRegisterType(registerType)
  764. ? formatCoilDisplayValue(rawValue)
  765. : (byteAddressed ? formatRawByteText(rawBytes) : formatRawWordText(rawWords)))
  766. const displayValue = rawValue === null
  767. ? (inputValue.trim() ? inputValue : '--')
  768. : formatRegisterValue({ ...register, dataType, byteOffset }, rawValue)
  769. return {
  770. address,
  771. addressRangeText: isBitRegisterType(registerType)
  772. ? `0x${padHex(address)}`
  773. : (byteAddressed
  774. ? formatAddressRange(address, Math.max(1, byteLength))
  775. : formatAddressRange(address, registerCount)),
  776. addressText: isBitField
  777. ? (byteAddressed
  778. ? `${formatAddressRange(address, Math.max(1, byteLength))}.b${bitOffset}${bitWidth > 1 ? `..b${bitOffset + bitWidth - 1}` : ''}`
  779. : formatBitFieldAddressText(address, byteOffset, bitOffset, bitWidth))
  780. : (byteAddressed
  781. ? formatAddressRange(address, Math.max(1, byteLength))
  782. : formatRegisterAddressText(address, byteOffset, byteLength, registerType)),
  783. bitOffset: isBitField ? bitOffset : '',
  784. bitWidth: isBitField ? bitWidth : '',
  785. byteLength,
  786. byteLengthText: isBitRegisterType(registerType)
  787. ? '1bit'
  788. : (isBitField
  789. ? (bitWidth === 1 ? `1bit/占${byteLength}B` : `${bitWidth}bit/占${byteLength}B`)
  790. : (textByteLength && textByteLength !== byteLength ? `${textByteLength}B/占${byteLength}B` : `${byteLength}B`)),
  791. byteStart: Math.max(0, Math.floor(Number(register.byteStart) || 0)),
  792. dataType,
  793. dataTypeIndex: getDataTypeIndex(dataType),
  794. dataTypeText: getRegisterValueTypeLabel(dataType),
  795. defaultValue,
  796. displayValue,
  797. id: register.id || createId('gm-reg'),
  798. inputType: isTextRegister(dataType) ? 'text' : 'text',
  799. inputValue,
  800. isStructField: layout === GROUP_LAYOUT_STRUCT || !!register.isStructField,
  801. layout,
  802. isBitField,
  803. isDirty: !!register.isDirty,
  804. maxValue: normalizeTextValue(register.maxValue),
  805. minValue: normalizeTextValue(register.minValue),
  806. name: register.name || `寄存器 ${index + 1}`,
  807. rawValue,
  808. rawValueText,
  809. rawBytes,
  810. rawWords,
  811. registerCount,
  812. byteOffset,
  813. registerType,
  814. showDataType: !isBitRegisterType(registerType),
  815. showRange: !isBitRegisterType(registerType) && !isBitField && supportsRange(dataType),
  816. showTextLength: !isBitRegisterType(registerType) && isTextRegister(dataType),
  817. showUnit: canShowUnit,
  818. textByteLength,
  819. unit: canShowUnit ? normalizeTextValue(register.unit).trim() : '',
  820. structByteLength: register.structByteLength,
  821. remark: register.remark || '',
  822. ...pickFields(register, SOURCE_REGISTER_FIELDS),
  823. sourceMetaText: createRegisterSourceMetaText(register)
  824. }
  825. }
  826. function normalizeGroup(group) {
  827. const registerType = getRegisterType(group.registerType || group.type || DEFAULT_REGISTER_TYPE)
  828. const startAddress = normalizeAddress(group.startAddress, 0)
  829. const maxQuantity = getMaxQuantity(registerType.key)
  830. const sourceRegisters = Array.isArray(group.registers) ? group.registers : []
  831. const layout = isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER
  832. const byteAddressed = isByteAddressedGroup(group)
  833. const hasExplicitQuantity = group.quantity !== undefined && group.quantity !== null && group.quantity !== ''
  834. const quantity = hasExplicitQuantity
  835. ? clampInteger(group.quantity, 1, maxQuantity, 1)
  836. : clampInteger(sourceRegisters.length || 1, 1, maxQuantity, 1)
  837. const baseGroup = {
  838. deleteVisible: !!group.deleteVisible,
  839. expanded: group.expanded === true,
  840. id: group.id || createId('gm-group'),
  841. isStructLayout: layout === GROUP_LAYOUT_STRUCT,
  842. layout,
  843. name: String(group.name || group.groupName || '寄存器组').trim() || '寄存器组',
  844. quantity,
  845. registerType: registerType.key,
  846. startAddress,
  847. touchStartX: 0,
  848. ...pickFields(group, SOURCE_GROUP_FIELDS)
  849. }
  850. const registers = []
  851. let nextAddress = startAddress
  852. let nextByteOffset = 0
  853. for (let index = 0; index < quantity; index += 1) {
  854. const sourceRegister = sourceRegisters[index] || {}
  855. let normalizedSourceRegister = sourceRegister
  856. const dataType = normalizeRegisterDataType(sourceRegister, baseGroup.registerType)
  857. const textByteLength = isTextRegister(dataType)
  858. ? normalizeTextByteLength(sourceRegister.textByteLength, DEFAULT_TEXT_BYTE_LENGTH)
  859. : ''
  860. const isBitRegister = isBitRegisterType(baseGroup.registerType)
  861. let address = startAddress + index
  862. let byteOffset = 0
  863. if (!isBitRegister) {
  864. const explicitByteStart = Number(sourceRegister.byteStart)
  865. const hasExplicitByteStart = layout === GROUP_LAYOUT_STRUCT && Number.isFinite(explicitByteStart)
  866. const byteLength = getRegisterByteLength(dataType, { ...sourceRegister, layout, textByteLength })
  867. if (layout !== GROUP_LAYOUT_STRUCT && !isByteRegister(dataType) && nextByteOffset % 2 !== 0) {
  868. nextByteOffset += 1
  869. }
  870. const currentByteStart = hasExplicitByteStart
  871. ? Math.max(0, Math.floor(explicitByteStart))
  872. : nextByteOffset
  873. address = byteAddressed ? startAddress + currentByteStart : startAddress + Math.floor(currentByteStart / 2)
  874. byteOffset = byteAddressed ? 0 : currentByteStart % 2
  875. normalizedSourceRegister = {
  876. ...sourceRegister,
  877. byteStart: currentByteStart
  878. }
  879. nextByteOffset = Math.max(nextByteOffset, currentByteStart + byteLength)
  880. }
  881. const register = normalizeRegister(normalizedSourceRegister, baseGroup, index, address, byteOffset)
  882. registers.push(register)
  883. if (isBitRegister) nextAddress += register.registerCount
  884. }
  885. const byteLength = isBitRegisterType(baseGroup.registerType)
  886. ? Math.max(1, nextAddress - startAddress)
  887. : Math.max(1, nextByteOffset)
  888. const paddedByteLength = isBitRegisterType(baseGroup.registerType)
  889. ? byteLength
  890. : (byteAddressed ? byteLength : alignEvenByteLength(byteLength))
  891. const wordQuantity = isBitRegisterType(baseGroup.registerType)
  892. ? Math.max(1, nextAddress - startAddress)
  893. : (byteAddressed ? Math.max(1, byteLength) : Math.max(1, paddedByteLength / 2))
  894. const addressSpan = byteAddressed ? Math.max(1, byteLength) : wordQuantity
  895. const addressOverflow = isAddressRangeOverflow(startAddress, addressSpan)
  896. const endAddress = startAddress + addressSpan - 1
  897. return {
  898. ...baseGroup,
  899. addressRangeText: formatAddressRange(startAddress, addressSpan),
  900. addressOverflow,
  901. addressWarningText: addressOverflow ? '地址超出 0xFFFF' : '',
  902. endAddressText: addressOverflow ? `0x${padHex(MAX_MODBUS_ADDRESS)}+` : `0x${padHex(endAddress)}`,
  903. functionCode: registerType.functionCode,
  904. isReadOnly: !registerType.writable,
  905. byteLength,
  906. maxQuantity,
  907. registerTypeIndex: getRegisterTypeIndex(registerType.key),
  908. registerTypeText: registerType.label,
  909. registers,
  910. paddedByteLength,
  911. sourceMetaText: createGroupSourceMetaText(baseGroup),
  912. startAddressText: `0x${padHex(startAddress)}`,
  913. wordQuantity,
  914. writable: registerType.writable
  915. }
  916. }
  917. function normalizeGroupConfig(config = {}) {
  918. const registerType = config.registerTypeIndex !== undefined && config.registerTypeIndex !== null
  919. ? (REGISTER_TYPE_OPTIONS[Number(config.registerTypeIndex)] || REGISTER_TYPE_OPTIONS[0])
  920. : getRegisterType(config.registerType || config.type || DEFAULT_REGISTER_TYPE)
  921. const maxQuantity = getMaxQuantity(registerType.key)
  922. return {
  923. layout: isStructLayout(config.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
  924. name: String(config.name || config.groupName || '寄存器组').trim() || '寄存器组',
  925. quantity: parseConfigQuantity(config.quantity, maxQuantity),
  926. registerType: registerType.key,
  927. startAddress: parseConfigAddress(config.startAddress)
  928. }
  929. }
  930. function getRegisterJsonValue(register) {
  931. if (register.inputValue !== undefined && register.inputValue !== null) {
  932. return normalizeTextValue(register.inputValue)
  933. }
  934. if (register.defaultValue !== undefined && register.defaultValue !== null) {
  935. return normalizeTextValue(register.defaultValue)
  936. }
  937. if (register.displayValue !== undefined && register.displayValue !== null && register.displayValue !== '--') {
  938. return normalizeTextValue(register.displayValue)
  939. }
  940. return ''
  941. }
  942. function normalizeImportedRegisterDataType(register) {
  943. const dataType = register.dataType || register.type || DEFAULT_DATA_TYPE
  944. return getDataType(dataType).key
  945. }
  946. function cloneImportedGroup(group) {
  947. return {
  948. layout: isStructLayout(group.layout) ? GROUP_LAYOUT_STRUCT : GROUP_LAYOUT_REGISTER,
  949. name: group.name,
  950. quantity: group.quantity,
  951. registerType: group.registerType || group.type || DEFAULT_REGISTER_TYPE,
  952. registers: (Array.isArray(group.registers) ? group.registers : []).map((register) => ({
  953. dataType: normalizeImportedRegisterDataType(register),
  954. defaultValue: register.defaultValue,
  955. inputValue: register.inputValue,
  956. maxValue: register.maxValue,
  957. minValue: register.minValue,
  958. name: register.name,
  959. isStructField: !!register.isStructField,
  960. textByteLength: register.textByteLength,
  961. remark: register.remark,
  962. unit: register.unit,
  963. value: register.value,
  964. ...pickFields(register, STRUCT_REGISTER_FIELDS),
  965. ...pickFields(register, SOURCE_REGISTER_FIELDS)
  966. })),
  967. startAddress: group.startAddress,
  968. ...pickFields(group, SOURCE_GROUP_FIELDS)
  969. }
  970. }
  971. function splitWordSpans(startAddress, quantity, maxQuantity) {
  972. const spans = []
  973. let address = normalizeAddress(startAddress, 0)
  974. let remaining = Math.max(0, Math.floor(Number(quantity) || 0))
  975. while (remaining > 0) {
  976. const spanQuantity = Math.min(remaining, maxQuantity)
  977. spans.push({
  978. address,
  979. quantity: spanQuantity
  980. })
  981. address += spanQuantity
  982. remaining -= spanQuantity
  983. }
  984. return spans
  985. }
  986. function getRegisterWriteValueText(register) {
  987. if (register.inputValue !== undefined && register.inputValue !== null) return normalizeTextValue(register.inputValue)
  988. if (register.defaultValue !== undefined && register.defaultValue !== null) return normalizeTextValue(register.defaultValue)
  989. return ''
  990. }
  991. function getRegisterWordsFromWordCache(register, wordCache) {
  992. const words = []
  993. for (let offset = 0; offset < register.registerCount; offset += 1) {
  994. const word = wordCache[register.address + offset]
  995. if (word === undefined) return null
  996. words.push(word)
  997. }
  998. return words
  999. }
  1000. function getRegisterBytesFromByteCache(register, byteCache) {
  1001. const bytes = []
  1002. for (let offset = 0; offset < register.byteLength; offset += 1) {
  1003. const byte = byteCache[register.address + offset]
  1004. if (byte === undefined) return null
  1005. bytes.push(Number(byte) & 0xFF)
  1006. }
  1007. return bytes
  1008. }
  1009. function getRegisterWordsFromByteCache(register, byteCache) {
  1010. const bytes = getRegisterBytesFromByteCache(register, byteCache)
  1011. if (!bytes) return null
  1012. return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
  1013. }
  1014. function decodeRegisterFromBytes(register, bytes) {
  1015. if (!Array.isArray(bytes) || bytes.length < register.byteLength) return null
  1016. return decodeRegisterValue(
  1017. {
  1018. ...register,
  1019. byteOffset: 0
  1020. },
  1021. bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
  1022. )
  1023. }
  1024. function decodeRegisterFromWordCache(register, wordCache) {
  1025. const words = getRegisterWordsFromWordCache(register, wordCache)
  1026. if (!words) return null
  1027. return decodeRegisterValue(register, words)
  1028. }
  1029. function decodeRegisterFromByteCache(register, byteCache) {
  1030. const bytes = getRegisterBytesFromByteCache(register, byteCache)
  1031. if (!bytes) return null
  1032. return decodeRegisterFromBytes(register, bytes)
  1033. }
  1034. function getRegisterEncodedWords(register) {
  1035. return encodeRegisterWords({
  1036. ...register,
  1037. inputValue: getRegisterWriteValueText(register)
  1038. })
  1039. }
  1040. function getRegisterEncodedBytes(register) {
  1041. return encodeRegisterBytes({
  1042. ...register,
  1043. inputValue: getRegisterWriteValueText(register)
  1044. })
  1045. }
  1046. function getRegisterByteStart(register, groupStartAddress = 0) {
  1047. if (Number.isFinite(Number(register.byteStart))) {
  1048. return Math.max(0, Math.floor(Number(register.byteStart)))
  1049. }
  1050. return Math.max(0, ((Number(register.address) || 0) - (Number(groupStartAddress) || 0)) * 2 + (Number(register.byteOffset) || 0))
  1051. }
  1052. function getGroupEncodedBytes(group, baseBytes = null) {
  1053. const byteLength = Math.max(2, Number(group && group.paddedByteLength) || ((Number(group && group.wordQuantity) || 1) * 2))
  1054. const bytes = Array.isArray(baseBytes)
  1055. ? baseBytes.slice(0, byteLength).map((byte) => Number(byte) & 0xFF)
  1056. : []
  1057. const registers = Array.isArray(group && group.registers) ? group.registers : []
  1058. while (bytes.length < byteLength) {
  1059. bytes.push(0)
  1060. }
  1061. registers.forEach((register) => {
  1062. const byteStart = getRegisterByteStart(register, group.startAddress)
  1063. if (isBitFieldRegister(register)) {
  1064. const encodedBytes = encodeBitFieldIntoBytes(
  1065. {
  1066. ...register,
  1067. inputValue: getRegisterWriteValueText(register)
  1068. },
  1069. bytes,
  1070. byteStart
  1071. )
  1072. if (!Array.isArray(encodedBytes)) {
  1073. throw new Error(`${register.name || '位域'} 没有有效写入值`)
  1074. }
  1075. return
  1076. }
  1077. const registerBytes = encodeRegisterBytes(register)
  1078. if (!Array.isArray(registerBytes) || !registerBytes.length) {
  1079. throw new Error(`${register.name || '寄存器'} 没有有效写入值`)
  1080. }
  1081. for (let offset = 0; offset < register.byteLength; offset += 1) {
  1082. if (byteStart + offset < bytes.length) {
  1083. bytes[byteStart + offset] = Number(registerBytes[offset] || 0) & 0xFF
  1084. }
  1085. }
  1086. })
  1087. return bytes
  1088. }
  1089. function getGroupEncodedWords(group, baseBytes = null) {
  1090. return bytesToWords(getGroupEncodedBytes(group, baseBytes))
  1091. }
  1092. function validateRegisterValue(register, value) {
  1093. const valueText = normalizeTextValue(
  1094. value === undefined || value === null ? getRegisterWriteValueText(register) : value
  1095. ).trim()
  1096. if (!valueText || valueText === '--') return true
  1097. if (registerTypeIsBit(register)) {
  1098. if (parseCoilValue(valueText) === null) {
  1099. throw new Error(`${register.name || '线圈'} 只能填写 0 或 1`)
  1100. }
  1101. return true
  1102. }
  1103. if (isBitFieldRegister(register)) {
  1104. if (parseBitFieldValue(register, valueText) === null) {
  1105. throw new Error(`${register.name || '位域'} 输入值无效`)
  1106. }
  1107. return true
  1108. }
  1109. const dataType = getDataType(register.dataType).key
  1110. if (isTextRegister(dataType)) {
  1111. encodeTextBytes(valueText, dataType, getEncodeByteLimit(register))
  1112. return true
  1113. }
  1114. const numberValue = parseNumberText(valueText, dataType)
  1115. if (numberValue === null) {
  1116. throw new Error(`${register.name || '寄存器'} 输入值无效`)
  1117. }
  1118. return validateNumericValue(register, numberValue)
  1119. }
  1120. function registerTypeIsBit(register) {
  1121. return !!register && isBitRegisterType(register.registerType)
  1122. }
  1123. module.exports = {
  1124. DATA_TYPE_OPTIONS,
  1125. DEFAULT_DATA_TYPE,
  1126. DEFAULT_REGISTER_TYPE,
  1127. GROUP_LAYOUT_REGISTER,
  1128. GROUP_LAYOUT_STRUCT,
  1129. MAX_MODBUS_ADDRESS,
  1130. REGISTER_TYPE_OPTIONS,
  1131. cloneImportedGroup,
  1132. decodeRegisterFromByteCache,
  1133. decodeRegisterFromWordCache,
  1134. decodeRegisterValue,
  1135. formatCoilDisplayValue,
  1136. formatRegisterValue,
  1137. getDataType,
  1138. getRegisterEncodedBytes,
  1139. getRegisterEncodedWords,
  1140. getGroupEncodedBytes,
  1141. getGroupEncodedWords,
  1142. getRegisterWordCount,
  1143. getRegisterJsonValue,
  1144. getRegisterBytesFromByteCache,
  1145. getRegisterWordsFromByteCache,
  1146. getRegisterWordsFromWordCache,
  1147. getRegisterWriteValueText,
  1148. isAddressRangeOverflow,
  1149. isBitRegisterType,
  1150. isByteRegister,
  1151. isTextRegister,
  1152. normalizeGroup,
  1153. normalizeGroupConfig,
  1154. normalizeRegister,
  1155. parseCoilValue,
  1156. registerTypeIsBit,
  1157. splitWordSpans,
  1158. validateRegisterValue
  1159. }