control-page-state.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. const {
  2. controlButtonRegisters,
  3. motorParameterInputRegisters,
  4. readonlyParamRegisters,
  5. speedCommandRegister,
  6. statusRegisters
  7. } = require('./registers')
  8. const {
  9. getSharedInputDefault,
  10. mergeInputValues,
  11. setSharedInputValues,
  12. updateDriverParams,
  13. toFiniteNumber
  14. } = require('./calculation-context')
  15. const {
  16. calculateParameterInputWriteValue,
  17. calculateSpeedCommandWriteValue,
  18. SCALE_MAX,
  19. formatFixedValue
  20. } = require('./conversions')
  21. const {
  22. updateStatusRegisterWords
  23. } = require('./status-format')
  24. const {
  25. floatToWords,
  26. getRegisterWordCache,
  27. toAddressKey,
  28. wordsToFloat
  29. } = require('./register-value-utils')
  30. const {
  31. appendInputUnit
  32. } = require('./input-value-utils')
  33. const AUTO_READ_MIN_INTERVAL = 100
  34. const AUTO_READ_MAX_INTERVAL = 3000
  35. const DEFAULT_AUTO_READ_INTERVAL = 1000
  36. const MOTOR_PARAM_START_ADDRESS = 0x60
  37. const MOTOR_PARAM_WORD_COUNT = 8
  38. const DRIVER_PARAM_START_ADDRESS = 0xA0
  39. const STATUS_START_ADDRESS = 0xC0
  40. function getRegisterSpanWordCount(registers, startAddress) {
  41. const endAddress = registers.reduce((maxAddress, item) => {
  42. const address = parseInt(item.address, 16)
  43. if (!Number.isFinite(address)) return maxAddress
  44. return Math.max(maxAddress, address + (item.registerCount || 1))
  45. }, startAddress)
  46. return endAddress - startAddress
  47. }
  48. const DRIVER_PARAM_WORD_COUNT = getRegisterSpanWordCount(readonlyParamRegisters, DRIVER_PARAM_START_ADDRESS)
  49. const STATUS_WORD_COUNT = getRegisterSpanWordCount(statusRegisters, STATUS_START_ADDRESS)
  50. function getInputValues(registers) {
  51. return registers.reduce((result, item) => {
  52. result[item.name] = toFiniteNumber(item.inputValue, getSharedInputDefault(item.name))
  53. return result
  54. }, {})
  55. }
  56. function updateMotorWriteValues(registers) {
  57. const inputValues = getInputValues(registers)
  58. return registers.map((item) => ({
  59. ...item,
  60. writeValue: calculateParameterInputWriteValue(item, item.inputValue, inputValues)
  61. }))
  62. }
  63. function toRegisterWord(value) {
  64. const numberValue = toFiniteNumber(value, NaN)
  65. if (!Number.isFinite(numberValue)) return null
  66. const wordValue = Math.round(numberValue)
  67. return wordValue >= 0 && wordValue <= 0xFFFF ? wordValue : null
  68. }
  69. function formatReadInputValue(item, value) {
  70. if (!Number.isFinite(value)) return ''
  71. if (item.name === 'LD' || item.name === 'LQ') return formatFixedValue(value, 6)
  72. if (item.name === 'RS') return formatFixedValue(value, 4)
  73. if (item.type === 'float') return formatFixedValue(value, 2)
  74. return String(Math.round(value))
  75. }
  76. function formatHexWord(value) {
  77. return `0x${(Number(value) & 0xFFFF).toString(16).toUpperCase().padStart(4, '0')}`
  78. }
  79. function wordsToAscii(words, startIndex, byteLength) {
  80. const chars = []
  81. const wordCount = Math.ceil(byteLength / 2)
  82. for (let index = 0; index < wordCount; index += 1) {
  83. const word = Number(words[startIndex + index])
  84. if (!Number.isInteger(word)) break
  85. const bytes = [(word >> 8) & 0xFF, word & 0xFF]
  86. for (const byte of bytes) {
  87. if (chars.length >= byteLength || byte === 0) {
  88. return chars.join('').trim() || '--'
  89. }
  90. if (byte >= 0x20 && byte <= 0x7E) {
  91. chars.push(String.fromCharCode(byte))
  92. }
  93. }
  94. }
  95. return chars.join('').trim() || '--'
  96. }
  97. function clampNumber(value, minValue, maxValue, fallback) {
  98. const numberValue = toFiniteNumber(value, NaN)
  99. if (!Number.isFinite(numberValue)) return fallback
  100. return Math.min(Math.max(Math.round(numberValue), minValue), maxValue)
  101. }
  102. function cloneRegister(item) {
  103. return {
  104. ...item
  105. }
  106. }
  107. function createInitialState() {
  108. return {
  109. autoReadInterval: DEFAULT_AUTO_READ_INTERVAL,
  110. autoReadStatus: false,
  111. connectedDevice: null,
  112. controlActionButtons: controlButtonRegisters.filter((item) => item.momentary).map(cloneRegister),
  113. controlButtons: controlButtonRegisters.filter((item) => !item.momentary).map(cloneRegister),
  114. errorText: '',
  115. isAwaitingResponse: false,
  116. isReadingDriver: false,
  117. isReadingMotor: false,
  118. isSending: false,
  119. isWritingMotor: false,
  120. motorParameterInputRegisters,
  121. readonlyParamRegisters,
  122. speedCommand: speedCommandRegister,
  123. systemTip: ''
  124. }
  125. }
  126. function applyTransportState(data, transportState) {
  127. const nextState = {
  128. connectedDevice: transportState.connectedDevice,
  129. errorText: transportState.errorText,
  130. isAwaitingResponse: transportState.isAwaitingResponse,
  131. isSending: transportState.isSending,
  132. systemTip: transportState.systemTip
  133. }
  134. if (!transportState.connectedDevice && data.autoReadStatus) {
  135. nextState.autoReadStatus = false
  136. }
  137. if (!transportState.connectedDevice) {
  138. nextState.isReadingDriver = false
  139. nextState.isReadingMotor = false
  140. nextState.isWritingMotor = false
  141. }
  142. return nextState
  143. }
  144. function applyMotorParameterInput(data, index, value) {
  145. const changedRegisters = data.motorParameterInputRegisters.map((item, currentIndex) => {
  146. if (currentIndex !== index) return item
  147. return {
  148. ...item,
  149. isDirty: true,
  150. inputValue: value
  151. }
  152. })
  153. const nextRegisters = updateMotorWriteValues(changedRegisters)
  154. const inputValues = mergeInputValues(nextRegisters)
  155. setSharedInputValues(nextRegisters)
  156. return {
  157. motorParameterInputRegisters: nextRegisters,
  158. speedCommand: {
  159. ...data.speedCommand,
  160. isDirty: true,
  161. writeValue: calculateSpeedCommandWriteValue(data.speedCommand.inputValue, inputValues)
  162. }
  163. }
  164. }
  165. function applySpeedCommandInput(data, inputValue) {
  166. const inputValues = mergeInputValues(data.motorParameterInputRegisters)
  167. return {
  168. speedCommand: {
  169. ...data.speedCommand,
  170. isDirty: true,
  171. inputValue,
  172. writeValue: calculateSpeedCommandWriteValue(inputValue, inputValues)
  173. }
  174. }
  175. }
  176. function applySpeedCommandReadValue(data, rawValue) {
  177. const wordValue = Number(rawValue)
  178. if (!Number.isInteger(wordValue)) return {}
  179. const inputValues = mergeInputValues(data.motorParameterInputRegisters)
  180. const speedBase = toFiniteNumber(inputValues['速度基准'])
  181. const inputValue = speedBase > 0
  182. ? appendInputUnit(data.speedCommand, formatFixedValue(wordValue / SCALE_MAX * speedBase, 2))
  183. : data.speedCommand.inputValue
  184. return {
  185. speedCommand: {
  186. ...data.speedCommand,
  187. isDirty: false,
  188. inputValue,
  189. writeValue: String(wordValue & 0xFFFF)
  190. }
  191. }
  192. }
  193. function getControlButtonWriteValue(button) {
  194. if (!button) return 0
  195. return button.writeValue
  196. }
  197. function getNextControlButton(button) {
  198. if (button.momentary) return button
  199. return {
  200. ...button,
  201. name: button.nextName,
  202. nextName: button.name,
  203. nextWriteValue: button.writeValue,
  204. writeValue: button.nextWriteValue
  205. }
  206. }
  207. function applyControlSuccess(data, button) {
  208. if (!button) return {}
  209. if (button.momentary) {
  210. return {
  211. systemTip: `${button.name}已下发`
  212. }
  213. }
  214. return {
  215. controlButtons: data.controlButtons.map((item) => (
  216. item.key === button.key ? getNextControlButton(item) : item
  217. )),
  218. systemTip: `${button.name}已下发`
  219. }
  220. }
  221. function getControlButtonFromRead(button, value) {
  222. if (!button || button.momentary) return button
  223. const readValue = Number(value)
  224. if (!Number.isFinite(readValue)) return button
  225. if (Number(button.writeValue) === readValue) return getNextControlButton(button)
  226. if (Number(button.nextWriteValue) === readValue) return button
  227. return button
  228. }
  229. function applyControlReadValues(data, coilValues = {}) {
  230. return {
  231. controlButtons: data.controlButtons.map((item) => {
  232. const value = coilValues[toAddressKey(item.address)]
  233. return value === undefined ? item : getControlButtonFromRead(item, value)
  234. })
  235. }
  236. }
  237. function buildMotorMainWriteValues(data) {
  238. const registerMap = data.motorParameterInputRegisters.reduce((result, item) => {
  239. result[item.name] = item
  240. return result
  241. }, {})
  242. const ldWords = floatToWords(registerMap.LD && registerMap.LD.inputValue)
  243. const lqWords = floatToWords(registerMap.LQ && registerMap.LQ.inputValue)
  244. const rsWords = floatToWords(registerMap.RS && registerMap.RS.inputValue)
  245. const polePairsWord = toRegisterWord(registerMap['极对数'] && registerMap['极对数'].inputValue)
  246. const speedBaseWord = toRegisterWord(registerMap['速度基准'] && registerMap['速度基准'].inputValue)
  247. if (!ldWords || !lqWords || !rsWords || !Number.isInteger(polePairsWord) || !Number.isInteger(speedBaseWord)) {
  248. return {
  249. errorText: '请检查 LD、LQ、RS、极对数和速度基准的输入值',
  250. values: null
  251. }
  252. }
  253. return {
  254. errorText: '',
  255. values: ldWords.concat(lqWords, rsWords, [polePairsWord, speedBaseWord])
  256. }
  257. }
  258. function applyMotorParameterReadValues(data, registerWordCache) {
  259. const nextRegisters = data.motorParameterInputRegisters.map((item) => {
  260. let readValue = null
  261. if (item.name === 'LD' && registerWordCache[0x60] !== undefined && registerWordCache[0x61] !== undefined) {
  262. readValue = wordsToFloat(registerWordCache[0x60], registerWordCache[0x61])
  263. } else if (item.name === 'LQ' && registerWordCache[0x62] !== undefined && registerWordCache[0x63] !== undefined) {
  264. readValue = wordsToFloat(registerWordCache[0x62], registerWordCache[0x63])
  265. } else if (item.name === 'RS' && registerWordCache[0x64] !== undefined && registerWordCache[0x65] !== undefined) {
  266. readValue = wordsToFloat(registerWordCache[0x64], registerWordCache[0x65])
  267. } else if (item.name === '极对数' && registerWordCache[0x66] !== undefined) {
  268. readValue = registerWordCache[0x66]
  269. } else if (item.name === '速度基准' && registerWordCache[0x67] !== undefined) {
  270. readValue = registerWordCache[0x67]
  271. }
  272. if (readValue === null) return item
  273. return {
  274. ...item,
  275. isDirty: false,
  276. inputValue: appendInputUnit(item, formatReadInputValue(item, readValue))
  277. }
  278. })
  279. const nextWriteRegisters = updateMotorWriteValues(nextRegisters)
  280. const inputValues = mergeInputValues(nextWriteRegisters)
  281. setSharedInputValues(nextWriteRegisters)
  282. return {
  283. motorParameterInputRegisters: nextWriteRegisters,
  284. speedCommand: {
  285. ...data.speedCommand,
  286. writeValue: calculateSpeedCommandWriteValue(data.speedCommand.inputValue, inputValues)
  287. }
  288. }
  289. }
  290. function clearMotorParameterDirty(data) {
  291. return {
  292. motorParameterInputRegisters: data.motorParameterInputRegisters.map((item) => ({
  293. ...item,
  294. isDirty: false
  295. }))
  296. }
  297. }
  298. function clearSpeedCommandDirty(data) {
  299. return {
  300. speedCommand: {
  301. ...data.speedCommand,
  302. isDirty: false
  303. }
  304. }
  305. }
  306. function applyMotorParameterBlur(data, index, value) {
  307. const item = data.motorParameterInputRegisters[index]
  308. if (!item) return {}
  309. return applyMotorParameterInput(data, index, appendInputUnit(item, value === undefined ? item.inputValue : value))
  310. }
  311. function applySpeedCommandBlur(data, value) {
  312. return applySpeedCommandInput(
  313. data,
  314. appendInputUnit(data.speedCommand, value === undefined ? data.speedCommand.inputValue : value)
  315. )
  316. }
  317. function applyDriverParameterReadValues(data, words) {
  318. if (!Array.isArray(words) || words.length < DRIVER_PARAM_WORD_COUNT) return {}
  319. const carrierFrequencyKHz = (words[0] >> 8) & 0xFF
  320. const baseVoltage = (words[0] & 0xFF) / 10
  321. const opAmpGain = words[1] & 0xFFFF
  322. const samplingResistorMohm = words[2] & 0xFFFF
  323. const busVoltageDividerRatio = wordsToFloat(words[4], words[5])
  324. const analogInputDividerRatio = wordsToFloat(words[6], words[7])
  325. const displayValues = {
  326. 芯片型号: wordsToAscii(words, 8, 8),
  327. 型号: wordsToAscii(words, 12, 16),
  328. 载波频率: String(carrierFrequencyKHz),
  329. 基准电压: formatFixedValue(baseVoltage, 2),
  330. 运放倍数: String(opAmpGain),
  331. 采样电阻: String(samplingResistorMohm),
  332. '全区 Flash 校验码': formatHexWord(words[3]),
  333. 母线电压分压比: formatFixedValue(busVoltageDividerRatio, 2),
  334. 模拟输入电压分压比: formatFixedValue(analogInputDividerRatio, 2)
  335. }
  336. updateDriverParams({
  337. analogInputDividerRatio,
  338. baseVoltage,
  339. busVoltageDividerRatio,
  340. carrierFrequencyKHz,
  341. opAmpGain,
  342. samplingResistorMohm
  343. })
  344. return {
  345. readonlyParamRegisters: data.readonlyParamRegisters.map((item) => ({
  346. ...item,
  347. displayValue: displayValues[item.name] || item.displayValue || '--'
  348. }))
  349. }
  350. }
  351. function applyStatusReadValues(words, startAddress = STATUS_START_ADDRESS) {
  352. if (!Array.isArray(words) || !words.length) return {}
  353. updateStatusRegisterWords(statusRegisters, startAddress, words)
  354. return {}
  355. }
  356. module.exports = {
  357. AUTO_READ_MAX_INTERVAL,
  358. AUTO_READ_MIN_INTERVAL,
  359. DRIVER_PARAM_START_ADDRESS,
  360. DRIVER_PARAM_WORD_COUNT,
  361. MOTOR_PARAM_START_ADDRESS,
  362. MOTOR_PARAM_WORD_COUNT,
  363. STATUS_START_ADDRESS,
  364. STATUS_WORD_COUNT,
  365. applyControlReadValues,
  366. applyControlSuccess,
  367. applyDriverParameterReadValues,
  368. applyMotorParameterBlur,
  369. applyMotorParameterInput,
  370. applyMotorParameterReadValues,
  371. clearMotorParameterDirty,
  372. clearSpeedCommandDirty,
  373. applySpeedCommandBlur,
  374. applySpeedCommandInput,
  375. applySpeedCommandReadValue,
  376. applyStatusReadValues,
  377. applyTransportState,
  378. buildMotorMainWriteValues,
  379. clampNumber,
  380. createInitialState,
  381. getControlButtonWriteValue,
  382. getRegisterWordCache,
  383. setSharedInputValues
  384. }