params.js 19 KB

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