params.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. const {
  2. createParameterDialogState,
  3. createParameterRegisterDialogState,
  4. findParameterGroup,
  5. findParameterRegister,
  6. getActiveParameterGroup,
  7. getPageState,
  8. getSettingsPageState,
  9. getTransportPageState,
  10. getVisiblePageState,
  11. resolveActiveParamView
  12. } = require('../../features/parameter-groups/view-model.js')
  13. const {
  14. createDialogHandlers,
  15. createParameterGroupPoller,
  16. parameterGroupService
  17. } = require('../../features/parameter-groups/index.js')
  18. const settingsService = require('../../store/settings-store.js')
  19. const themeService = require('../../store/theme-store.js')
  20. const transport = require('../../transport/ble-core.js')
  21. const {
  22. createPageToast
  23. } = require('../../utils/page-toast.js')
  24. const {
  25. PARAMETER_REGISTER_DRAG_THRESHOLD_PX,
  26. buildActiveParameterRegisterRows,
  27. clampIndex,
  28. getFallbackDragRowOffsetPx,
  29. getWindowWidth,
  30. resolveDragTargetIndex
  31. } = require('../../features/parameter-groups/drag-view-model.js')
  32. function getParameterGroupsFromState(state = {}) {
  33. return state.parameterGroups || []
  34. }
  35. function isReadableParameterGroup(group = {}, data = {}) {
  36. if (!group || group.addressOverflow) return false
  37. if (data.isStorageAccessProtocol) return !!group.sourceMemoryArea
  38. return !!group.functionCode
  39. }
  40. function isStorageScalarEntryKind(group = {}) {
  41. const entryKind = String(group.sourceEntryKind || '').trim().toLowerCase()
  42. return entryKind === 'enum' || entryKind === 'variable'
  43. }
  44. function isStorageNavigableGroup(group = {}) {
  45. if (!group || !group.sourceMemoryArea) return false
  46. if (isStorageScalarEntryKind(group)) return false
  47. return group.sourceEntryKind === 'array'
  48. || group.sourceEntryKind === 'struct'
  49. || group.isStructLayout
  50. || (Array.isArray(group.registers) && group.registers.length > 1)
  51. }
  52. Page({
  53. data: {
  54. ...getPageState(),
  55. activeParamView: 'parameterGroups',
  56. activeParameterGroupId: '',
  57. activeParameterRegisterRows: [],
  58. parameterDialog: createParameterDialogState()
  59. },
  60. onTabItemTap() {
  61. this.backToParamsHome()
  62. },
  63. onLoad() {
  64. this.pageToast = createPageToast(this, this.data)
  65. this.parameterGroupPoller = createParameterGroupPoller(() => this.data)
  66. this.parameterGroupTouchStarts = {}
  67. this.parameterWindowWidth = getWindowWidth()
  68. parameterGroupService.init()
  69. themeService.init()
  70. settingsService.init()
  71. this.unsubscribeTheme = themeService.subscribe((themeState) => {
  72. this.setData(themeState)
  73. })
  74. this.unsubscribeTransport = transport.subscribe((transportState) => {
  75. this.setData(getTransportPageState(transportState))
  76. if (transportState.connectedDevice) {
  77. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  78. } else {
  79. this.clearParameterAutoTimers()
  80. }
  81. })
  82. this.unsubscribeParameterGroups = parameterGroupService.subscribe((parameterState) => {
  83. const activeParameterGroup = getActiveParameterGroup(getParameterGroupsFromState(parameterState), this.data.activeParameterGroupId)
  84. this.setData({
  85. ...parameterState,
  86. activeParameterGroup,
  87. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag),
  88. activeParamView: this.data.activeParamView === 'parameterGroup' && !activeParameterGroup
  89. ? 'parameterGroups'
  90. : this.data.activeParamView
  91. })
  92. })
  93. this.unsubscribeSettings = settingsService.subscribe((settingsState) => {
  94. const protocolChanged = this.data.protocolMode && this.data.protocolMode !== settingsState.protocolMode
  95. const nextState = getSettingsPageState(this.data, settingsState)
  96. const activeParamView = protocolChanged ? 'parameterGroups' : nextState.activeParamView
  97. const parameterGroups = getParameterGroupsFromState(parameterGroupService.getState())
  98. const activeParameterGroupId = activeParamView === 'parameterGroup' && !protocolChanged
  99. ? this.data.activeParameterGroupId
  100. : ''
  101. const activeParameterGroup = getActiveParameterGroup(parameterGroups, activeParameterGroupId)
  102. const safeActiveView = activeParamView === 'parameterGroup' && !activeParameterGroup
  103. ? 'parameterGroups'
  104. : activeParamView
  105. this.clearParameterAutoTimers()
  106. this.setData({
  107. ...nextState,
  108. parameterGroups,
  109. activeParameterGroupId,
  110. activeParamView: safeActiveView,
  111. activeParameterGroup,
  112. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
  113. })
  114. if (safeActiveView === 'parameterGroups' || safeActiveView === 'parameterGroup') {
  115. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  116. } else {
  117. this.clearParameterAutoTimers()
  118. }
  119. })
  120. },
  121. onShow() {
  122. if (this.pageToast) {
  123. this.pageToast.setActive(true)
  124. }
  125. const pageState = getVisiblePageState(this.data)
  126. const activeParameterGroupId = pageState.activeParamView === 'parameterGroup'
  127. ? this.data.activeParameterGroupId
  128. : ''
  129. const activeParameterGroup = getActiveParameterGroup(getParameterGroupsFromState(pageState), activeParameterGroupId)
  130. this.setData({
  131. ...pageState,
  132. activeParameterGroup,
  133. activeParameterGroupId,
  134. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
  135. })
  136. this.pageToast.showFromState(pageState)
  137. this.scheduleVisibleParameterAutoReads()
  138. },
  139. onHide() {
  140. if (this.pageToast) {
  141. this.pageToast.setActive(false)
  142. }
  143. this.clearParameterRegisterDrag()
  144. this.clearParameterAutoTimers()
  145. },
  146. onUnload() {
  147. if (this.pageToast) {
  148. this.pageToast.destroy()
  149. this.pageToast = null
  150. }
  151. if (this.unsubscribeTheme) {
  152. this.unsubscribeTheme()
  153. this.unsubscribeTheme = null
  154. }
  155. if (this.unsubscribeTransport) {
  156. this.unsubscribeTransport()
  157. this.unsubscribeTransport = null
  158. }
  159. if (this.unsubscribeParameterGroups) {
  160. this.unsubscribeParameterGroups()
  161. this.unsubscribeParameterGroups = null
  162. }
  163. if (this.unsubscribeSettings) {
  164. this.unsubscribeSettings()
  165. this.unsubscribeSettings = null
  166. }
  167. this.clearParameterAutoTimers()
  168. },
  169. openParamView(event) {
  170. if (this.pageToast) this.pageToast.clear()
  171. this.closeParameterDraft()
  172. this.clearParameterRegisterDrag()
  173. const activeParamView = event.currentTarget.dataset.view
  174. if (!activeParamView) return
  175. this.setData({
  176. activeParamView
  177. })
  178. if (activeParamView === 'parameterGroups') {
  179. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  180. }
  181. },
  182. openParameterGroup(event) {
  183. const groupId = event.currentTarget.dataset.groupId
  184. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  185. if (!group) return
  186. if (this.parameterGroupLongPressGuard === groupId) {
  187. this.parameterGroupLongPressGuard = ''
  188. return
  189. }
  190. if (this.data.isStorageAccessProtocol && !isStorageNavigableGroup(group)) return
  191. if (!this.data.isStorageAccessProtocol && group.sourceMetadataLocked) return
  192. if (this.pageToast) this.pageToast.clear()
  193. this.closeParameterDraft()
  194. if (this.data.parameterCardControlEnabled === false) {
  195. parameterGroupService.setGroupExpanded(groupId, !group.expanded)
  196. return
  197. }
  198. this.setData({
  199. activeParameterGroup: group,
  200. activeParameterGroupId: groupId,
  201. activeParamView: 'parameterGroup',
  202. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, this.parameterRegisterDrag)
  203. })
  204. },
  205. openStorageScalarRegisterEdit(event) {
  206. const groupId = event.currentTarget.dataset.groupId
  207. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  208. const register = group && group.registers ? group.registers[0] : null
  209. if (!group || !group.isStorageScalarGroup || !register) return
  210. if (this.pageToast) this.pageToast.clear()
  211. this.parameterGroupLongPressGuard = groupId
  212. this.setData({
  213. parameterDialog: createParameterRegisterDialogState('editRegister', group, register, 0)
  214. })
  215. },
  216. openStorageParameterGroupDetail(event) {
  217. if (!this.data.isStorageAccessProtocol) return
  218. const groupId = event.currentTarget.dataset.groupId
  219. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  220. if (!group || !group.sourceMemoryArea) return
  221. if (group.isStorageScalarGroup) {
  222. this.openStorageScalarRegisterEdit(event)
  223. return
  224. }
  225. this.parameterGroupLongPressGuard = groupId
  226. this.openParameterGroupEdit(event)
  227. },
  228. backToParamsHome() {
  229. if (this.pageToast) this.pageToast.clear()
  230. this.closeParameterDraft()
  231. this.clearParameterRegisterDrag()
  232. this.clearParameterAutoTimers()
  233. const activeParamView = resolveActiveParamView('', this.data)
  234. this.setData({
  235. activeParameterGroup: null,
  236. activeParameterGroupId: '',
  237. activeParamView,
  238. activeParameterRegisterRows: []
  239. })
  240. if (activeParamView === 'parameterGroups') {
  241. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  242. }
  243. },
  244. noop() {},
  245. ...createDialogHandlers(parameterGroupService),
  246. async importParameterGroupsJson() {
  247. const count = await parameterGroupService.importJsonFromMessageFile()
  248. if (count && this.pageToast) this.pageToast.show(`已导入 ${count} 个寄存器组`)
  249. },
  250. async completeParameterStructs() {
  251. const result = await parameterGroupService.completeStructInstanceGroupsWithStructFile()
  252. if (result && result.completedCount && this.pageToast) {
  253. this.pageToast.show(`已补全 ${result.completedCount} 个结构体/枚举项`)
  254. }
  255. },
  256. clearStorageAccessGroups() {
  257. if (!this.data.isStorageAccessProtocol) return
  258. this.clearParameterAutoTimers()
  259. parameterGroupService.clearStorageAccessGroups()
  260. this.setData({
  261. activeParameterGroup: null,
  262. activeParameterGroupId: '',
  263. activeParameterRegisterRows: [],
  264. activeParamView: 'parameterGroups'
  265. })
  266. if (this.pageToast) this.pageToast.show('已清除结构体组与变量')
  267. },
  268. async saveParameterGroupsJson() {
  269. const result = await parameterGroupService.saveJsonToChat()
  270. const count = Number(result && result.count) || 0
  271. if (count && this.pageToast) {
  272. const actionText = result.method === 'disk' ? '已保存' : '已发送'
  273. this.pageToast.show(`${actionText} ${count} 个寄存器组`)
  274. }
  275. },
  276. onParameterRegisterValueInput(event) {
  277. parameterGroupService.updateRegisterValue(
  278. event.currentTarget.dataset.groupId,
  279. Number(event.currentTarget.dataset.index),
  280. event.detail.value
  281. )
  282. },
  283. async onParameterRegisterValueBlur(event) {
  284. const groupId = event.currentTarget.dataset.groupId
  285. const registerIndex = Number(event.currentTarget.dataset.index)
  286. try {
  287. parameterGroupService.validateRegisterInputValue(groupId, registerIndex, event.detail.value)
  288. } catch (error) {
  289. if (this.pageToast) this.pageToast.show(error.message || '输入值无效', 'error')
  290. return
  291. }
  292. if (!this.data.isStorageAccessProtocol || !this.data.connectedDevice) return
  293. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  294. const register = group && group.registers ? group.registers[registerIndex] : null
  295. if (!group || !register || !register.isDirty || !group.writable || group.addressOverflow) return
  296. this.clearParameterAutoTimers()
  297. const ok = await parameterGroupService.writeRegister(groupId, registerIndex)
  298. if (this.data.parameterAutoPollEnabled) {
  299. this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
  300. }
  301. if (ok && this.pageToast) {
  302. this.pageToast.show(`${register.displayName || register.name || '变量'}已写入`)
  303. }
  304. },
  305. async readAllParameterGroups() {
  306. if (!this.data.connectedDevice) return
  307. const protocolMode = this.data.protocolMode
  308. const groups = getParameterGroupsFromState(this.data)
  309. .filter((group) => isReadableParameterGroup(group, this.data))
  310. if (!groups.length) {
  311. if (this.pageToast) this.pageToast.show('没有可读取的参数组')
  312. return
  313. }
  314. this.clearParameterAutoTimers()
  315. let successCount = 0
  316. for (const group of groups) {
  317. if (!this.data.connectedDevice || this.data.protocolMode !== protocolMode) break
  318. const ok = await parameterGroupService.readGroup(group.id, {
  319. maxPacketLength: this.data.parameterMaxPacketLength,
  320. protocolMode
  321. })
  322. if (ok) successCount += 1
  323. }
  324. if (this.data.parameterAutoPollEnabled) {
  325. this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
  326. }
  327. if (this.pageToast) {
  328. this.pageToast.show(`读取完成 ${successCount}/${groups.length} 个`)
  329. }
  330. },
  331. async readParameterGroup(event) {
  332. if (!this.data.connectedDevice) return
  333. const groupId = event.currentTarget.dataset.groupId
  334. const ok = await parameterGroupService.readGroup(groupId, {
  335. maxPacketLength: this.data.parameterMaxPacketLength
  336. })
  337. if (ok && this.pageToast) this.pageToast.show('参数组读取完成')
  338. },
  339. async writeParameterGroup(event) {
  340. if (!this.data.connectedDevice) return
  341. const groupId = event.currentTarget.dataset.groupId
  342. const ok = await parameterGroupService.writeGroup(groupId)
  343. if (ok && this.pageToast) this.pageToast.show('参数组写入完成')
  344. },
  345. onParameterGroupTouchStart(event) {
  346. const groupId = event.currentTarget.dataset.groupId
  347. const touch = (event.changedTouches || [])[0]
  348. if (!groupId || !touch) return
  349. this.parameterGroupTouchStarts[groupId] = touch.clientX
  350. },
  351. onParameterGroupTouchEnd(event) {
  352. const groupId = event.currentTarget.dataset.groupId
  353. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  354. const touch = (event.changedTouches || [])[0]
  355. const startX = this.parameterGroupTouchStarts[groupId]
  356. if (!groupId || !group || group.expanded || !touch || !Number.isFinite(startX)) return
  357. const deltaX = touch.clientX - startX
  358. if (deltaX > 42) {
  359. parameterGroupService.setGroupDeleteVisible(groupId, true)
  360. } else if (deltaX < -24) {
  361. parameterGroupService.setGroupDeleteVisible(groupId, false)
  362. }
  363. },
  364. onParameterRegisterDragStart(event) {
  365. const touch = (event.changedTouches || [])[0]
  366. if (!touch) return
  367. const groupId = event.currentTarget.dataset.groupId
  368. const index = Number(event.currentTarget.dataset.index)
  369. const activeParameterGroup = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  370. if (!groupId || !activeParameterGroup || !Number.isInteger(index)) return
  371. this.parameterRegisterDrag = {
  372. groupId,
  373. index,
  374. moved: false,
  375. rowCenters: [],
  376. rowOffset: getFallbackDragRowOffsetPx(this.parameterWindowWidth),
  377. startY: touch.clientY,
  378. targetIndex: index,
  379. translateY: 0
  380. }
  381. if (this.data.activeParameterGroupId === groupId) {
  382. this.setData({
  383. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
  384. })
  385. }
  386. this.measureParameterRegisterRows(this.parameterRegisterDrag)
  387. },
  388. onParameterRegisterDragMove(event) {
  389. const touch = (event.changedTouches || [])[0]
  390. if (!touch || !this.parameterRegisterDrag) return
  391. const drag = this.parameterRegisterDrag
  392. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  393. if (!group) return
  394. const translateY = Math.round(touch.clientY - drag.startY)
  395. const moved = Math.abs(translateY) > PARAMETER_REGISTER_DRAG_THRESHOLD_PX
  396. const targetIndex = moved
  397. ? resolveDragTargetIndex(drag, touch.clientY, group.registers.length)
  398. : drag.index
  399. if (
  400. drag.translateY === translateY
  401. && drag.moved === moved
  402. && drag.targetIndex === targetIndex
  403. ) {
  404. return
  405. }
  406. drag.translateY = translateY
  407. drag.moved = moved
  408. drag.targetIndex = targetIndex
  409. if (this.data.activeParameterGroupId === group.id) {
  410. this.setData({
  411. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, drag)
  412. })
  413. }
  414. },
  415. onParameterRegisterDragEnd(event) {
  416. const drag = this.parameterRegisterDrag
  417. this.parameterRegisterDrag = null
  418. if (!drag || !drag.groupId) return
  419. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  420. if (!group) return
  421. if (this.data.activeParameterGroupId === group.id) {
  422. this.setData({
  423. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  424. })
  425. }
  426. if (!drag.moved) return
  427. const targetIndex = clampIndex(
  428. Number(drag.targetIndex) || drag.index,
  429. 0,
  430. group.registers.length - 1
  431. )
  432. if (targetIndex === drag.index) return
  433. const updatedGroup = parameterGroupService.reorderRegister(drag.groupId, drag.index, targetIndex)
  434. if (!updatedGroup) return
  435. this.parameterRegisterLongPressGuard = `${drag.groupId}:${targetIndex}`
  436. setTimeout(() => {
  437. if (this.parameterRegisterLongPressGuard === `${drag.groupId}:${targetIndex}`) {
  438. this.parameterRegisterLongPressGuard = ''
  439. }
  440. }, 260)
  441. if (this.data.activeParameterGroupId === updatedGroup.id) {
  442. this.setData({
  443. activeParameterGroup: updatedGroup,
  444. activeParameterRegisterRows: buildActiveParameterRegisterRows(updatedGroup, null)
  445. })
  446. }
  447. },
  448. onParameterRegisterDragCancel() {
  449. const drag = this.parameterRegisterDrag
  450. this.parameterRegisterDrag = null
  451. if (!drag || !drag.groupId) return
  452. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  453. if (!group || this.data.activeParameterGroupId !== group.id) return
  454. this.setData({
  455. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  456. })
  457. },
  458. deleteParameterGroup(event) {
  459. const groupId = event.currentTarget.dataset.groupId
  460. this.clearParameterAutoTimer(groupId)
  461. parameterGroupService.removeGroup(groupId)
  462. if (this.data.activeParameterGroupId === groupId) {
  463. this.setData({
  464. activeParameterGroup: null,
  465. activeParameterGroupId: '',
  466. activeParamView: 'parameterGroups'
  467. })
  468. }
  469. if (this.pageToast) this.pageToast.show('寄存器组已删除')
  470. },
  471. clearParameterAutoTimer(groupId) {
  472. if (this.parameterGroupPoller) this.parameterGroupPoller.clearTimer(groupId)
  473. },
  474. clearParameterAutoTimers() {
  475. if (this.parameterGroupPoller) this.parameterGroupPoller.clearAll()
  476. },
  477. scheduleVisibleParameterAutoReads() {
  478. if (this.parameterGroupPoller) this.parameterGroupPoller.scheduleVisible()
  479. },
  480. scheduleParameterAutoPoll(delay) {
  481. if (this.parameterGroupPoller) this.parameterGroupPoller.schedule(delay)
  482. },
  483. clearParameterRegisterDrag() {
  484. if (!this.parameterRegisterDrag) return
  485. const drag = this.parameterRegisterDrag
  486. this.parameterRegisterDrag = null
  487. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  488. this.setData({
  489. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  490. })
  491. },
  492. measureParameterRegisterRows(dragReference) {
  493. const query = this.createSelectorQuery()
  494. query.selectAll('.generic-register-row').boundingClientRect((rects) => {
  495. if (!this.parameterRegisterDrag || this.parameterRegisterDrag !== dragReference) return
  496. if (!Array.isArray(rects) || !rects.length) return
  497. dragReference.rowCenters = rects.map((rect) => Number(rect.top || 0) + Number(rect.height || 0) / 2)
  498. dragReference.rowOffset = Math.max(
  499. 1,
  500. Math.round(Number((rects[dragReference.index] || {}).height) || dragReference.rowOffset || 0)
  501. )
  502. const group = findParameterGroup(getParameterGroupsFromState(this.data), dragReference.groupId)
  503. if (!group || this.data.activeParameterGroupId !== group.id) return
  504. this.setData({
  505. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, dragReference)
  506. })
  507. }).exec()
  508. }
  509. })