params.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  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 (this.parameterGroupLongPressGuard === groupId) {
  174. this.parameterGroupLongPressGuard = ''
  175. return
  176. }
  177. if (this.pageToast) this.pageToast.clear()
  178. this.closeParameterDraft()
  179. if (this.data.parameterCardControlEnabled === false) {
  180. parameterGroupService.setGroupExpanded(groupId, !group.expanded)
  181. return
  182. }
  183. this.setData({
  184. activeParameterGroup: group,
  185. activeParameterGroupId: groupId,
  186. activeParamView: 'parameterGroup',
  187. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, this.parameterRegisterDrag)
  188. })
  189. },
  190. backToParamsHome() {
  191. if (this.pageToast) this.pageToast.clear()
  192. this.closeParameterDraft()
  193. this.clearParameterRegisterDrag()
  194. this.clearParameterAutoTimers()
  195. const activeParamView = resolveActiveParamView('', this.data)
  196. this.setData({
  197. activeParameterGroup: null,
  198. activeParameterGroupId: '',
  199. activeParamView,
  200. activeParameterRegisterRows: []
  201. })
  202. if (activeParamView === 'parameterGroups') {
  203. setTimeout(() => this.scheduleVisibleParameterAutoReads(), 0)
  204. }
  205. },
  206. noop() {},
  207. ...createDialogHandlers(parameterGroupService),
  208. async importParameterGroupsJson() {
  209. const count = await parameterGroupService.importJsonFromMessageFile()
  210. if (count && this.pageToast) this.pageToast.show(`已导入 ${count} 个寄存器组`)
  211. },
  212. async completeParameterStructs() {
  213. const result = await parameterGroupService.completeStructInstanceGroupsWithStructFile()
  214. if (result && result.completedCount && this.pageToast) {
  215. this.pageToast.show(`已补全 ${result.completedCount} 个结构体/枚举项`)
  216. }
  217. },
  218. clearStorageAccessGroups() {
  219. if (!this.data.isStorageAccessProtocol) return
  220. this.clearParameterAutoTimers()
  221. parameterGroupService.clearStorageAccessGroups()
  222. this.setData({
  223. activeParameterGroup: null,
  224. activeParameterGroupId: '',
  225. activeParameterRegisterRows: [],
  226. activeParamView: 'parameterGroups'
  227. })
  228. if (this.pageToast) this.pageToast.show('已清除结构体组与变量')
  229. },
  230. async saveParameterGroupsJson() {
  231. const result = await parameterGroupService.saveJsonToChat()
  232. const count = Number(result && result.count) || 0
  233. if (count && this.pageToast) {
  234. const actionText = result.method === 'disk' ? '已保存' : '已发送'
  235. this.pageToast.show(`${actionText} ${count} 个寄存器组`)
  236. }
  237. },
  238. onParameterRegisterValueInput(event) {
  239. parameterGroupService.updateRegisterValue(
  240. event.currentTarget.dataset.groupId,
  241. Number(event.currentTarget.dataset.index),
  242. event.detail.value
  243. )
  244. },
  245. async onParameterRegisterValueBlur(event) {
  246. const groupId = event.currentTarget.dataset.groupId
  247. const registerIndex = Number(event.currentTarget.dataset.index)
  248. try {
  249. parameterGroupService.validateRegisterInputValue(groupId, registerIndex, event.detail.value)
  250. } catch (error) {
  251. if (this.pageToast) this.pageToast.show(error.message || '输入值无效', 'error')
  252. return
  253. }
  254. if (!this.data.isStorageAccessProtocol || !this.data.connectedDevice) return
  255. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  256. const register = group && group.registers ? group.registers[registerIndex] : null
  257. if (!group || !register || !register.isDirty || !group.writable || group.addressOverflow) return
  258. this.clearParameterAutoTimers()
  259. const ok = await parameterGroupService.writeRegister(groupId, registerIndex)
  260. if (this.data.parameterAutoPollEnabled) {
  261. this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
  262. }
  263. if (ok && this.pageToast) {
  264. this.pageToast.show(`${register.name || '变量'}已写入`)
  265. }
  266. },
  267. async readAllParameterGroups() {
  268. if (!this.data.connectedDevice) return
  269. const protocolMode = this.data.protocolMode
  270. const groups = getParameterGroupsFromState(this.data)
  271. .filter((group) => isReadableParameterGroup(group, this.data))
  272. if (!groups.length) {
  273. if (this.pageToast) this.pageToast.show('没有可读取的参数组')
  274. return
  275. }
  276. this.clearParameterAutoTimers()
  277. let successCount = 0
  278. for (const group of groups) {
  279. if (!this.data.connectedDevice || this.data.protocolMode !== protocolMode) break
  280. const ok = await parameterGroupService.readGroup(group.id, {
  281. maxPacketLength: this.data.parameterMaxPacketLength,
  282. protocolMode
  283. })
  284. if (ok) successCount += 1
  285. }
  286. if (this.data.parameterAutoPollEnabled) {
  287. this.scheduleParameterAutoPoll(this.data.parameterPollInterval || 100)
  288. }
  289. if (this.pageToast) {
  290. this.pageToast.show(`读取完成 ${successCount}/${groups.length} 个`)
  291. }
  292. },
  293. async readParameterGroup(event) {
  294. if (!this.data.connectedDevice) return
  295. const groupId = event.currentTarget.dataset.groupId
  296. const ok = await parameterGroupService.readGroup(groupId, {
  297. maxPacketLength: this.data.parameterMaxPacketLength
  298. })
  299. if (ok && this.pageToast) this.pageToast.show('参数组读取完成')
  300. },
  301. async writeParameterGroup(event) {
  302. if (!this.data.connectedDevice) return
  303. const groupId = event.currentTarget.dataset.groupId
  304. const ok = await parameterGroupService.writeGroup(groupId)
  305. if (ok && this.pageToast) this.pageToast.show('参数组写入完成')
  306. },
  307. onParameterGroupTouchStart(event) {
  308. const groupId = event.currentTarget.dataset.groupId
  309. const touch = (event.changedTouches || [])[0]
  310. if (!groupId || !touch) return
  311. this.parameterGroupTouchStarts[groupId] = touch.clientX
  312. },
  313. onParameterGroupTouchEnd(event) {
  314. const groupId = event.currentTarget.dataset.groupId
  315. const group = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  316. const touch = (event.changedTouches || [])[0]
  317. const startX = this.parameterGroupTouchStarts[groupId]
  318. if (!groupId || !group || group.expanded || !touch || !Number.isFinite(startX)) return
  319. const deltaX = touch.clientX - startX
  320. if (deltaX > 42) {
  321. parameterGroupService.setGroupDeleteVisible(groupId, true)
  322. } else if (deltaX < -24) {
  323. parameterGroupService.setGroupDeleteVisible(groupId, false)
  324. }
  325. },
  326. onParameterRegisterDragStart(event) {
  327. const touch = (event.changedTouches || [])[0]
  328. if (!touch) return
  329. const groupId = event.currentTarget.dataset.groupId
  330. const index = Number(event.currentTarget.dataset.index)
  331. const activeParameterGroup = findParameterGroup(getParameterGroupsFromState(this.data), groupId)
  332. if (!groupId || !activeParameterGroup || !Number.isInteger(index)) return
  333. this.parameterRegisterDrag = {
  334. groupId,
  335. index,
  336. moved: false,
  337. rowCenters: [],
  338. rowOffset: getFallbackDragRowOffsetPx(this.parameterWindowWidth),
  339. startY: touch.clientY,
  340. targetIndex: index,
  341. translateY: 0
  342. }
  343. if (this.data.activeParameterGroupId === groupId) {
  344. this.setData({
  345. activeParameterRegisterRows: buildActiveParameterRegisterRows(activeParameterGroup, this.parameterRegisterDrag)
  346. })
  347. }
  348. this.measureParameterRegisterRows(this.parameterRegisterDrag)
  349. },
  350. onParameterRegisterDragMove(event) {
  351. const touch = (event.changedTouches || [])[0]
  352. if (!touch || !this.parameterRegisterDrag) return
  353. const drag = this.parameterRegisterDrag
  354. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  355. if (!group) return
  356. const translateY = Math.round(touch.clientY - drag.startY)
  357. const moved = Math.abs(translateY) > PARAMETER_REGISTER_DRAG_THRESHOLD_PX
  358. const targetIndex = moved
  359. ? resolveDragTargetIndex(drag, touch.clientY, group.registers.length)
  360. : drag.index
  361. if (
  362. drag.translateY === translateY
  363. && drag.moved === moved
  364. && drag.targetIndex === targetIndex
  365. ) {
  366. return
  367. }
  368. drag.translateY = translateY
  369. drag.moved = moved
  370. drag.targetIndex = targetIndex
  371. if (this.data.activeParameterGroupId === group.id) {
  372. this.setData({
  373. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, drag)
  374. })
  375. }
  376. },
  377. onParameterRegisterDragEnd(event) {
  378. const drag = this.parameterRegisterDrag
  379. this.parameterRegisterDrag = null
  380. if (!drag || !drag.groupId) return
  381. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  382. if (!group) return
  383. if (this.data.activeParameterGroupId === group.id) {
  384. this.setData({
  385. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  386. })
  387. }
  388. if (!drag.moved) return
  389. const targetIndex = clampIndex(
  390. Number(drag.targetIndex) || drag.index,
  391. 0,
  392. group.registers.length - 1
  393. )
  394. if (targetIndex === drag.index) return
  395. const updatedGroup = parameterGroupService.reorderRegister(drag.groupId, drag.index, targetIndex)
  396. if (!updatedGroup) return
  397. this.parameterRegisterLongPressGuard = `${drag.groupId}:${targetIndex}`
  398. setTimeout(() => {
  399. if (this.parameterRegisterLongPressGuard === `${drag.groupId}:${targetIndex}`) {
  400. this.parameterRegisterLongPressGuard = ''
  401. }
  402. }, 260)
  403. if (this.data.activeParameterGroupId === updatedGroup.id) {
  404. this.setData({
  405. activeParameterGroup: updatedGroup,
  406. activeParameterRegisterRows: buildActiveParameterRegisterRows(updatedGroup, null)
  407. })
  408. }
  409. },
  410. onParameterRegisterDragCancel() {
  411. const drag = this.parameterRegisterDrag
  412. this.parameterRegisterDrag = null
  413. if (!drag || !drag.groupId) return
  414. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  415. if (!group || this.data.activeParameterGroupId !== group.id) return
  416. this.setData({
  417. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  418. })
  419. },
  420. deleteParameterGroup(event) {
  421. const groupId = event.currentTarget.dataset.groupId
  422. this.clearParameterAutoTimer(groupId)
  423. parameterGroupService.removeGroup(groupId)
  424. if (this.data.activeParameterGroupId === groupId) {
  425. this.setData({
  426. activeParameterGroup: null,
  427. activeParameterGroupId: '',
  428. activeParamView: 'parameterGroups'
  429. })
  430. }
  431. if (this.pageToast) this.pageToast.show('寄存器组已删除')
  432. },
  433. clearParameterAutoTimer(groupId) {
  434. if (this.parameterGroupPoller) this.parameterGroupPoller.clearTimer(groupId)
  435. },
  436. clearParameterAutoTimers() {
  437. if (this.parameterGroupPoller) this.parameterGroupPoller.clearAll()
  438. },
  439. scheduleVisibleParameterAutoReads() {
  440. if (this.parameterGroupPoller) this.parameterGroupPoller.scheduleVisible()
  441. },
  442. scheduleParameterAutoPoll(delay) {
  443. if (this.parameterGroupPoller) this.parameterGroupPoller.schedule(delay)
  444. },
  445. clearParameterRegisterDrag() {
  446. if (!this.parameterRegisterDrag) return
  447. const drag = this.parameterRegisterDrag
  448. this.parameterRegisterDrag = null
  449. const group = findParameterGroup(getParameterGroupsFromState(this.data), drag.groupId)
  450. this.setData({
  451. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, null)
  452. })
  453. },
  454. measureParameterRegisterRows(dragReference) {
  455. const query = this.createSelectorQuery()
  456. query.selectAll('.generic-register-row').boundingClientRect((rects) => {
  457. if (!this.parameterRegisterDrag || this.parameterRegisterDrag !== dragReference) return
  458. if (!Array.isArray(rects) || !rects.length) return
  459. dragReference.rowCenters = rects.map((rect) => Number(rect.top || 0) + Number(rect.height || 0) / 2)
  460. dragReference.rowOffset = Math.max(
  461. 1,
  462. Math.round(Number((rects[dragReference.index] || {}).height) || dragReference.rowOffset || 0)
  463. )
  464. const group = findParameterGroup(getParameterGroupsFromState(this.data), dragReference.groupId)
  465. if (!group || this.data.activeParameterGroupId !== group.id) return
  466. this.setData({
  467. activeParameterRegisterRows: buildActiveParameterRegisterRows(group, dragReference)
  468. })
  469. }).exec()
  470. }
  471. })