1
0

value-codec.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. const {
  2. normalizeTextValue
  3. } = require('../../utils/base-utils.js')
  4. const {
  5. bytesToWords,
  6. wordsToBytes
  7. } = require('../../utils/binary-utils.js')
  8. const {
  9. alignEvenByteLength,
  10. getBitFieldByteLength,
  11. getBitFieldMaxValue,
  12. getDataType,
  13. getDataTypeIndex,
  14. getEncodeByteLimit,
  15. getRegisterByteLength,
  16. getRegisterTextByteLength,
  17. getRegisterValueTypeLabel,
  18. getRegisterWordCount,
  19. getRegisterWordCountAtOffset,
  20. isBitFieldRegister,
  21. isBitRegisterType,
  22. isByteRegister,
  23. isHexRegister,
  24. isNumericRegister,
  25. isRawRegister,
  26. isTextRegister,
  27. normalizeBitOffset,
  28. normalizeBitWidth,
  29. normalizeTextByteLength,
  30. supportsRange,
  31. supportsUnit
  32. } = require('./value-types.js')
  33. const {
  34. decodeTextBytes,
  35. encodeTextBytes
  36. } = require('./value-text.js')
  37. const {
  38. bytesToFloatValue,
  39. bytesToSignedInteger,
  40. bytesToUnsignedInteger,
  41. floatToBytes,
  42. formatFloatValue,
  43. formatHexValue,
  44. formatIntegerValue,
  45. parseNumberText,
  46. unsignedIntegerToBytes,
  47. validateNumericValue
  48. } = require('./value-number.js')
  49. function padWordHex(value) {
  50. return Number(value || 0).toString(16).toUpperCase().padStart(4, '0')
  51. }
  52. function formatRawWordText(words = []) {
  53. if (!Array.isArray(words) || !words.length) return '--'
  54. return words.map((word) => `0x${padWordHex(word)}`).join(' ')
  55. }
  56. function formatRawByteText(bytes = []) {
  57. if (!Array.isArray(bytes) || !bytes.length) return '--'
  58. return bytes.map((byte) => `0x${(Number(byte) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}`).join(' ')
  59. }
  60. function formatRawByteTextWithDefault(bytes = [], byteLength = 1) {
  61. const safeLength = Math.max(1, Math.floor(Number(byteLength) || 1))
  62. const source = Array.isArray(bytes) ? bytes : []
  63. return Array.from({ length: safeLength }, (_, index) => (
  64. `0x${(Number(source[index] || 0) & 0xFF).toString(16).toUpperCase().padStart(2, '0')}`
  65. )).join(' ')
  66. }
  67. function parseCoilValue(value) {
  68. const text = String(value === undefined || value === null ? '' : value).trim()
  69. if (!text || text === '--') return null
  70. if (['1', 'true', 'TRUE', 'on', 'ON', '开'].includes(text)) return 1
  71. if (['0', 'false', 'FALSE', 'off', 'OFF', '关'].includes(text)) return 0
  72. const coilValue = Number(text)
  73. return Number.isFinite(coilValue) ? (coilValue ? 1 : 0) : null
  74. }
  75. function normalizeEnumOptions(register = {}) {
  76. return (Array.isArray(register.enumOptions) ? register.enumOptions : [])
  77. .map((option) => ({
  78. label: String(option && (option.label || option.name) || '').trim(),
  79. name: String(option && (option.name || option.label) || '').trim(),
  80. value: Number(option && option.value)
  81. }))
  82. .filter((option) => option.label && Number.isFinite(option.value))
  83. }
  84. function findEnumOptionByText(register, valueText) {
  85. const text = normalizeTextValue(valueText).trim()
  86. if (!text) return null
  87. const normalizedText = text.toLowerCase()
  88. return normalizeEnumOptions(register).find((option) => (
  89. option.name.toLowerCase() === normalizedText
  90. || option.label.toLowerCase() === normalizedText
  91. || `${option.label}(${option.value})`.toLowerCase() === normalizedText
  92. || `${option.label} (${option.value})`.toLowerCase() === normalizedText
  93. || `${option.label}(${option.value})`.toLowerCase() === normalizedText
  94. )) || null
  95. }
  96. function parseEnumValueText(register, valueText) {
  97. const option = findEnumOptionByText(register, valueText)
  98. return option ? option.value : null
  99. }
  100. function formatEnumValue(register, rawValue) {
  101. const numberValue = Number(rawValue)
  102. if (!Number.isFinite(numberValue)) return ''
  103. const option = normalizeEnumOptions(register).find((item) => Number(item.value) === numberValue)
  104. if (!option) return ''
  105. return `${option.label} (${numberValue})`
  106. }
  107. function parseBitFieldValue(register, valueText) {
  108. const text = normalizeTextValue(valueText).trim()
  109. const bitWidth = normalizeBitWidth(register.bitWidth)
  110. let parsed = parseEnumValueText(register, text)
  111. if (parsed === null) parsed = parseNumberText(text, 'uint32_t')
  112. const maxValue = getBitFieldMaxValue(register)
  113. if (parsed === null && bitWidth === 1) parsed = parseCoilValue(text)
  114. if (parsed === null) return null
  115. if (Math.round(parsed) !== parsed || parsed < 0 || parsed > maxValue) {
  116. throw new Error(`${register.name || '位域'} 超出 0 - ${maxValue} 范围`)
  117. }
  118. return Math.round(parsed)
  119. }
  120. function decodeBitFieldBytes(register, bytes = []) {
  121. const bitOffset = normalizeBitOffset(register.bitOffset)
  122. const bitWidth = normalizeBitWidth(register.bitWidth)
  123. let byteIndex = 0
  124. let currentBitOffset = bitOffset
  125. let multiplier = 1
  126. let remaining = bitWidth
  127. let value = 0
  128. while (remaining > 0 && byteIndex < bytes.length) {
  129. const take = Math.min(8 - currentBitOffset, remaining)
  130. const mask = (1 << take) - 1
  131. const part = ((Number(bytes[byteIndex]) & 0xFF) >> currentBitOffset) & mask
  132. value += part * multiplier
  133. multiplier *= Math.pow(2, take)
  134. remaining -= take
  135. byteIndex += 1
  136. currentBitOffset = 0
  137. }
  138. return remaining > 0 ? null : value
  139. }
  140. function encodeBitFieldIntoBytes(register, bytes, byteStart = 0) {
  141. const valueText = normalizeTextValue(register.inputValue)
  142. let value = parseBitFieldValue(register, valueText)
  143. const bitOffset = normalizeBitOffset(register.bitOffset)
  144. const bitWidth = normalizeBitWidth(register.bitWidth)
  145. let byteIndex = Math.max(0, Math.floor(Number(byteStart) || 0))
  146. let currentBitOffset = bitOffset
  147. let remaining = bitWidth
  148. if (value === null) return null
  149. while (remaining > 0) {
  150. const take = Math.min(8 - currentBitOffset, remaining)
  151. const mask = (1 << take) - 1
  152. const shiftedMask = (mask << currentBitOffset) & 0xFF
  153. const part = value & mask
  154. if (byteIndex >= bytes.length) return null
  155. bytes[byteIndex] = ((Number(bytes[byteIndex]) & 0xFF) & (~shiftedMask & 0xFF))
  156. | ((part << currentBitOffset) & shiftedMask)
  157. value = Math.floor(value / Math.pow(2, take))
  158. remaining -= take
  159. byteIndex += 1
  160. currentBitOffset = 0
  161. }
  162. return bytes
  163. }
  164. function encodeBitFieldBytes(register) {
  165. const bytes = Array.from({ length: getBitFieldByteLength(register) }, () => 0)
  166. return encodeBitFieldIntoBytes(register, bytes, 0)
  167. }
  168. function getRegisterDataBytes(register, words) {
  169. const dataType = getDataType(register.dataType).key
  170. const byteLength = getRegisterByteLength(dataType, register)
  171. const byteOffset = Math.max(0, Math.floor(Number(register.byteOffset) || 0))
  172. const sourceBytes = wordsToBytes(words, Math.max(0, (Array.isArray(words) ? words.length : 0) * 2))
  173. return sourceBytes.slice(byteOffset, byteOffset + byteLength)
  174. }
  175. function encodeRegisterBytes(register) {
  176. const dataType = getDataType(register.dataType).key
  177. const valueText = normalizeTextValue(register.inputValue)
  178. const byteLength = getRegisterByteLength(dataType, register)
  179. const memoryEndian = register.memoryEndian || 'big'
  180. if (isBitFieldRegister(register)) {
  181. return encodeBitFieldBytes(register)
  182. }
  183. if (isTextRegister(dataType)) {
  184. const byteLimit = getEncodeByteLimit(register)
  185. const bytes = encodeTextBytes(valueText, dataType, byteLimit)
  186. const paddedBytes = bytes.slice()
  187. while (paddedBytes.length < byteLength) {
  188. paddedBytes.push(0)
  189. }
  190. return paddedBytes.slice(0, byteLength)
  191. }
  192. if (isRawRegister(dataType)) return null
  193. let numberValue = parseEnumValueText(register, valueText)
  194. if (numberValue === null) numberValue = parseNumberText(valueText, dataType)
  195. if (numberValue === null) return null
  196. validateNumericValue(register, numberValue)
  197. if (dataType === 'float') return floatToBytes(numberValue, memoryEndian)
  198. const rounded = Math.round(numberValue)
  199. if (dataType === 'int8_t' || dataType === 'uint8_t') return [rounded & 0xFF]
  200. if (dataType === 'int16_t' || dataType === 'uint16_t' || dataType === 'hex') {
  201. return unsignedIntegerToBytes(rounded, 2, memoryEndian)
  202. }
  203. if (dataType === 'int32_t' || dataType === 'uint32_t') {
  204. return unsignedIntegerToBytes(rounded, 4, memoryEndian)
  205. }
  206. return unsignedIntegerToBytes(rounded, byteLength, memoryEndian)
  207. }
  208. function encodeRegisterWords(register) {
  209. const dataType = getDataType(register.dataType).key
  210. const bytes = encodeRegisterBytes(register)
  211. if (!Array.isArray(bytes)) return null
  212. if (isByteRegister(dataType)) return [bytes[0] & 0xFF]
  213. return bytesToWords(bytes.length % 2 === 0 ? bytes : bytes.concat(0))
  214. }
  215. function decodeRegisterValue(register, words) {
  216. const dataType = getDataType(register.dataType).key
  217. const memoryEndian = register.memoryEndian || 'big'
  218. if (!Array.isArray(words) || words.length < getRegisterWordCountAtOffset(dataType, register.byteOffset || 0, register)) return null
  219. const bytes = getRegisterDataBytes(register, words)
  220. const byteLength = getRegisterByteLength(dataType, register)
  221. if (bytes.length < byteLength) return null
  222. if (isBitFieldRegister(register)) {
  223. return decodeBitFieldBytes(register, bytes)
  224. }
  225. if (isTextRegister(dataType)) {
  226. return decodeTextBytes(bytes.slice(0, getEncodeByteLimit(register)), dataType)
  227. }
  228. if (isRawRegister(dataType)) {
  229. return bytes.slice(0, byteLength)
  230. }
  231. if (dataType === 'float') {
  232. return bytesToFloatValue(bytes, memoryEndian)
  233. }
  234. if (dataType === 'int8_t') {
  235. const byteValue = bytes[0] & 0xFF
  236. return byteValue & 0x80 ? byteValue - 0x100 : byteValue
  237. }
  238. if (dataType === 'uint8_t') {
  239. return bytes[0] & 0xFF
  240. }
  241. if (dataType === 'int16_t') {
  242. return bytesToSignedInteger(bytes.slice(0, 2), memoryEndian)
  243. }
  244. if (dataType === 'uint16_t') {
  245. return bytesToUnsignedInteger(bytes.slice(0, 2), memoryEndian)
  246. }
  247. if (dataType === 'hex') {
  248. return bytesToUnsignedInteger(bytes.slice(0, 2), memoryEndian)
  249. }
  250. if (dataType === 'int32_t') {
  251. return bytesToSignedInteger(bytes.slice(0, 4), memoryEndian)
  252. }
  253. return bytesToUnsignedInteger(bytes.slice(0, 4), memoryEndian)
  254. }
  255. function formatRegisterValue(register, rawValue) {
  256. if (rawValue === null || rawValue === undefined) return '--'
  257. const dataType = getDataType(register.dataType).key
  258. if (isRawRegister(dataType)) return formatRawByteText(Array.isArray(rawValue) ? rawValue : [])
  259. const enumValue = formatEnumValue(register, rawValue)
  260. if (enumValue) return enumValue
  261. if (isTextRegister(dataType)) return normalizeTextValue(rawValue)
  262. if (dataType === 'hex') return formatHexValue(rawValue)
  263. if (dataType === 'float') return formatFloatValue(rawValue)
  264. return formatIntegerValue(rawValue, dataType)
  265. }
  266. function formatCoilDisplayValue(value) {
  267. return Number(value) ? '1' : '0'
  268. }
  269. function registerTypeIsBit(register) {
  270. return !!register && isBitRegisterType(register.registerType)
  271. }
  272. function validateRegisterValue(register, value) {
  273. const valueText = normalizeTextValue(
  274. value === undefined || value === null ? '' : value
  275. ).trim()
  276. if (!valueText || valueText === '--') return true
  277. if (registerTypeIsBit(register)) {
  278. if (parseCoilValue(valueText) === null) {
  279. throw new Error(`${register.name || '线圈'} 只能填写 0 或 1`)
  280. }
  281. return true
  282. }
  283. if (isBitFieldRegister(register)) {
  284. if (parseBitFieldValue(register, valueText) === null) {
  285. throw new Error(`${register.name || '位域'} 输入值无效`)
  286. }
  287. return true
  288. }
  289. const dataType = getDataType(register.dataType).key
  290. if (isRawRegister(dataType)) {
  291. throw new Error(`${register.name || '寄存器'} 需要先配置数据类型`)
  292. }
  293. if (isTextRegister(dataType)) {
  294. encodeTextBytes(valueText, dataType, getEncodeByteLimit(register))
  295. return true
  296. }
  297. let numberValue = parseEnumValueText(register, valueText)
  298. if (numberValue === null) numberValue = parseNumberText(valueText, dataType)
  299. if (numberValue === null) {
  300. throw new Error(`${register.name || '寄存器'} 输入值无效`)
  301. }
  302. return validateNumericValue(register, numberValue)
  303. }
  304. module.exports = {
  305. alignEvenByteLength,
  306. decodeRegisterValue,
  307. encodeBitFieldIntoBytes,
  308. encodeRegisterBytes,
  309. encodeRegisterWords,
  310. encodeTextBytes,
  311. formatCoilDisplayValue,
  312. formatRawByteText,
  313. formatRawByteTextWithDefault,
  314. formatRawWordText,
  315. formatRegisterValue,
  316. normalizeEnumOptions,
  317. parseEnumValueText,
  318. getBitFieldByteLength,
  319. getBitFieldMaxValue,
  320. getDataType,
  321. getDataTypeIndex,
  322. getEncodeByteLimit,
  323. getRegisterByteLength,
  324. getRegisterTextByteLength,
  325. getRegisterValueTypeLabel,
  326. getRegisterWordCount,
  327. getRegisterWordCountAtOffset,
  328. isBitFieldRegister,
  329. isBitRegisterType,
  330. isByteRegister,
  331. isHexRegister,
  332. isNumericRegister,
  333. isRawRegister,
  334. isTextRegister,
  335. normalizeBitOffset,
  336. normalizeBitWidth,
  337. normalizeTextByteLength,
  338. parseCoilValue,
  339. parseNumberText,
  340. registerTypeIsBit,
  341. supportsRange,
  342. supportsUnit,
  343. validateRegisterValue
  344. }