params.js 18 KB

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