1
0

value-codec.js 13 KB

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