1
0

communication.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. const settingsService = require('../../store/settings-store.js')
  2. const themeService = require('../../store/theme-store.js')
  3. const transport = require('../../transport/ble-core.js')
  4. const {
  5. createPageToast
  6. } = require('../../utils/page-toast.js')
  7. const {
  8. parameterGroupService
  9. } = require('../../features/parameter-groups/index.js')
  10. const {
  11. LOG_MODE_OPTIONS,
  12. decorateLogs,
  13. executeStorageAccessProtocol,
  14. executeStorageAccessSpecialCommand,
  15. getLogModeToggleText,
  16. getManualStatePayload,
  17. getNextLogMode,
  18. getNextSerialMode,
  19. getProtocolModeState,
  20. getStorageAccessAreaOptions,
  21. manualRtuService,
  22. sendSerialFrame,
  23. normalizeStorageAccessState,
  24. normalizeSerialState
  25. } = require('../../features/communication/index.js')
  26. const INITIAL_SERIAL_STATE = normalizeSerialState({
  27. serialInputText: '',
  28. serialMode: 'hex'
  29. })
  30. Page({
  31. data: {
  32. ...themeService.getState(),
  33. ...settingsService.getState(),
  34. ...getProtocolModeState(settingsService.getState()),
  35. ...transport.getState(),
  36. ...parameterGroupService.getState(),
  37. ...manualRtuService.getState(),
  38. logs: decorateLogs(transport.getState().logs, 'hex'),
  39. logMode: 'hex',
  40. logModeToggleText: getLogModeToggleText('hex'),
  41. logModeOptions: LOG_MODE_OPTIONS,
  42. storageAccessAreaOptions: getStorageAccessAreaOptions(),
  43. ...normalizeStorageAccessState({
  44. storageAccessAreaIndex: 0,
  45. storageAccessAddress: '',
  46. storageAccessCommandIndex: 0,
  47. storageAccessDataText: '',
  48. storageAccessLength: ''
  49. }),
  50. serialTitleText: '串口发送',
  51. ...INITIAL_SERIAL_STATE,
  52. toastText: '',
  53. toastType: ''
  54. },
  55. onLoad() {
  56. this.pageToast = createPageToast(this, this.data)
  57. this.lastSyncedSlaveAddress = ''
  58. transport.init()
  59. parameterGroupService.init()
  60. settingsService.init()
  61. themeService.init()
  62. const settingsState = settingsService.getState()
  63. manualRtuService.setProtocolInput({
  64. slaveAddress: settingsState.modbusSlaveAddress
  65. })
  66. this.lastSyncedSlaveAddress = settingsState.modbusSlaveAddress
  67. this.unsubscribeTheme = themeService.subscribe((themeState) => {
  68. this.setData(themeState)
  69. })
  70. this.unsubscribeSettings = settingsService.subscribe((settingsState) => {
  71. this.setData({
  72. ...settingsState,
  73. ...getProtocolModeState(settingsState)
  74. })
  75. if (settingsState.modbusSlaveAddress !== this.lastSyncedSlaveAddress) {
  76. this.lastSyncedSlaveAddress = settingsState.modbusSlaveAddress
  77. manualRtuService.setProtocolInput({
  78. slaveAddress: settingsState.modbusSlaveAddress
  79. })
  80. }
  81. })
  82. this.unsubscribeTransport = transport.subscribe((transportState) => {
  83. this.setData({
  84. ...transportState,
  85. logs: decorateLogs(transportState.logs, this.data.logMode)
  86. })
  87. })
  88. this.unsubscribeParameterGroups = parameterGroupService.subscribe((parameterState) => {
  89. this.setData({
  90. ...parameterState,
  91. ...normalizeStorageAccessState(this.data, {
  92. storageAccessCommandIndex: this.data.storageAccessCommandIndex,
  93. storageAccessAreaIndex: this.data.storageAccessAreaIndex
  94. })
  95. })
  96. })
  97. this.unsubscribeManualRtu = manualRtuService.subscribe((manualState) => {
  98. this.setData(getManualStatePayload(manualState))
  99. })
  100. },
  101. onShow() {
  102. if (this.pageToast) {
  103. this.pageToast.setActive(true)
  104. }
  105. },
  106. onHide() {
  107. if (this.pageToast) {
  108. this.pageToast.setActive(false)
  109. }
  110. },
  111. onUnload() {
  112. if (this.pageToast) {
  113. this.pageToast.destroy()
  114. this.pageToast = null
  115. }
  116. if (this.unsubscribeTheme) {
  117. this.unsubscribeTheme()
  118. this.unsubscribeTheme = null
  119. }
  120. if (this.unsubscribeSettings) {
  121. this.unsubscribeSettings()
  122. this.unsubscribeSettings = null
  123. }
  124. if (this.unsubscribeTransport) {
  125. this.unsubscribeTransport()
  126. this.unsubscribeTransport = null
  127. }
  128. if (this.unsubscribeParameterGroups) {
  129. this.unsubscribeParameterGroups()
  130. this.unsubscribeParameterGroups = null
  131. }
  132. if (this.unsubscribeManualRtu) {
  133. this.unsubscribeManualRtu()
  134. this.unsubscribeManualRtu = null
  135. }
  136. },
  137. noop() {},
  138. switchSerialMode() {
  139. this.setData(normalizeSerialState(this.data, {
  140. serialMode: getNextSerialMode(this.data.serialMode)
  141. }))
  142. },
  143. switchLogMode(event) {
  144. const requestedMode = event && event.currentTarget && event.currentTarget.dataset
  145. ? event.currentTarget.dataset.mode
  146. : ''
  147. const mode = requestedMode || getNextLogMode(this.data.logMode)
  148. this.setData({
  149. logMode: mode,
  150. logModeToggleText: getLogModeToggleText(mode),
  151. logs: decorateLogs(transport.getState().logs, mode)
  152. })
  153. },
  154. clearCommunicationLogs() {
  155. transport.clearLogs()
  156. },
  157. onSerialInput(event) {
  158. this.setData(normalizeSerialState(this.data, {
  159. serialInputText: event.detail.value
  160. }))
  161. },
  162. clearSerialInput() {
  163. this.setData(normalizeSerialState(this.data, {
  164. serialInputText: ''
  165. }))
  166. },
  167. async sendSerialFrame() {
  168. try {
  169. const result = await sendSerialFrame(this.data)
  170. if (!result.ok) {
  171. if (this.pageToast) this.pageToast.show(result.errorText || '发送失败', 'error')
  172. return
  173. }
  174. if (this.pageToast) this.pageToast.show('已发送')
  175. this.setData({
  176. ...(result.serialState || {}),
  177. serialPreviewHex: result.previewHex || '',
  178. serialPreviewLengthText: `${(result.bytes || []).length} bytes`
  179. })
  180. } catch (error) {
  181. if (this.pageToast) this.pageToast.show(error.message || '发送失败', 'error')
  182. }
  183. },
  184. onProtocolCommandChange(event) {
  185. manualRtuService.setCommandIndex(Number(event.detail.value))
  186. },
  187. onProtocolInput(event) {
  188. const field = event.currentTarget.dataset.field
  189. if (!field) return
  190. manualRtuService.setProtocolInput({
  191. [field]: event.detail.value
  192. })
  193. },
  194. onProtocolCoilChange(event) {
  195. manualRtuService.setProtocolInput({
  196. coilEnabled: !!event.detail.value
  197. })
  198. },
  199. openProtocolMultipleDialog() {
  200. manualRtuService.openProtocolMultipleDialog()
  201. },
  202. closeProtocolMultipleDialog() {
  203. manualRtuService.closeProtocolMultipleDialog()
  204. },
  205. toggleProtocolMultipleExpanded() {
  206. manualRtuService.toggleProtocolMultipleExpanded()
  207. },
  208. onProtocolMultipleQuantityChange(event) {
  209. manualRtuService.setProtocolMultipleQuantity(event.detail.value)
  210. },
  211. onProtocolMultipleValueInput(event) {
  212. const index = Number(event.currentTarget.dataset.index)
  213. manualRtuService.setProtocolMultipleValue(index, event.detail.value)
  214. },
  215. onProtocolMultipleTypeChange(event) {
  216. const index = Number(event.currentTarget.dataset.index)
  217. manualRtuService.setProtocolMultipleType(index, Number(event.detail.value))
  218. },
  219. onProtocolMultipleTextLengthInput(event) {
  220. const index = Number(event.currentTarget.dataset.index)
  221. manualRtuService.setProtocolMultipleTextLength(index, event.detail.value)
  222. },
  223. async sendProtocolFrame(event) {
  224. const source = event && event.currentTarget && event.currentTarget.dataset
  225. ? event.currentTarget.dataset.source
  226. : ''
  227. if (this.data.protocolMultipleDialog && this.data.protocolMultipleDialog.visible && source !== 'dialog') {
  228. if (this.pageToast) this.pageToast.show('请在多值窗口内发送', 'error')
  229. return
  230. }
  231. if (!this.data.connectedDevice) {
  232. if (this.pageToast) this.pageToast.show('请先连接蓝牙设备', 'error')
  233. return
  234. }
  235. const response = await manualRtuService.sendGeneratedFrame()
  236. if (response && this.pageToast) {
  237. this.pageToast.show(response === true ? '已发送' : '已收到回复')
  238. } else if (this.pageToast) {
  239. const manualState = manualRtuService.getState()
  240. this.pageToast.show(manualState.protocolStatusText || '发送失败或超时未回复', 'error')
  241. }
  242. },
  243. switchStorageAccessCommandMode() {
  244. const currentIndex = Number(this.data.storageAccessCommandIndex) || 0
  245. const nextState = normalizeStorageAccessState(this.data, {
  246. storageAccessCommandIndex: currentIndex === 0 ? 1 : 0
  247. })
  248. this.setData(nextState)
  249. },
  250. onStorageAccessInput(event) {
  251. const field = event.currentTarget.dataset.field
  252. if (!field) return
  253. const changed = {
  254. storageAccessCommandIndex: this.data.storageAccessCommandIndex,
  255. [field]: event.detail.value
  256. }
  257. const nextState = normalizeStorageAccessState(this.data, changed)
  258. this.setData(nextState)
  259. },
  260. onStorageAccessAreaChange(event) {
  261. const storageAccessAreaIndex = Number(event.detail.value)
  262. const nextState = normalizeStorageAccessState(this.data, {
  263. storageAccessCommandIndex: this.data.storageAccessCommandIndex,
  264. storageAccessAreaIndex
  265. })
  266. this.setData(nextState)
  267. },
  268. async sendStorageAccessProtocolFrame() {
  269. try {
  270. if (!this.data.connectedDevice) {
  271. if (this.pageToast) this.pageToast.show('请先连接蓝牙设备', 'error')
  272. return
  273. }
  274. const result = await executeStorageAccessProtocol(this.data)
  275. if (!result.ok) {
  276. if (this.pageToast) this.pageToast.show(result.errorText || '操作失败', 'error')
  277. return
  278. }
  279. if (!this.data.storageAccessShowWriteData) {
  280. this.setData({
  281. storageAccessPreviewHexText: result.previewHex || ''
  282. })
  283. if (this.pageToast) this.pageToast.show(`读取完成 ${(result.bytes || []).length} bytes`)
  284. return
  285. }
  286. if (this.pageToast) this.pageToast.show('写入完成')
  287. } catch (error) {
  288. if (this.pageToast) this.pageToast.show(error.message || '操作失败', 'error')
  289. }
  290. },
  291. async syncStorageAccessCodeInfo() {
  292. if (!this.data.isStorageAccessProtocol) return
  293. if (!this.data.connectedDevice) {
  294. if (this.pageToast) this.pageToast.show('请先连接蓝牙设备', 'error')
  295. return
  296. }
  297. let result
  298. try {
  299. result = await parameterGroupService.syncFromStorageAccessCodeInfo({
  300. maxPacketLength: this.data.parameterMaxPacketLength
  301. })
  302. } catch (error) {
  303. if (this.pageToast) this.pageToast.show(error.message || 'CodeInfo 同步失败', 'error')
  304. return
  305. }
  306. if (!result || !result.ok) {
  307. if (this.pageToast && result && result.errorText) this.pageToast.show(result.errorText, 'error')
  308. return
  309. }
  310. this.setData(normalizeStorageAccessState(this.data, {
  311. storageAccessCommandIndex: this.data.storageAccessCommandIndex,
  312. storageAccessAreaIndex: 0
  313. }))
  314. const codeInfoAddressText = result.codeInfoAddressText || Number(result.codeInfoAddress || 0).toString(16).toUpperCase()
  315. const codeInfoByteLengthText = result.codeInfoByteLengthText || Number(result.codeInfoByteLength || 0).toString(16).toUpperCase()
  316. const addedCount = Number(result.addedGroups || 0) + Number(result.addedRegisters || 0)
  317. const updatedCount = Number(result.updatedGroups || 0) + Number(result.updatedRegisters || 0)
  318. const changedText = [
  319. addedCount ? `新增 ${addedCount}` : '',
  320. updatedCount ? `更新 ${updatedCount}` : ''
  321. ].filter(Boolean).join(',')
  322. if (this.pageToast) {
  323. this.pageToast.show(`同步完成 codeInfo 0x${codeInfoAddressText} / 0x${codeInfoByteLengthText},${result.structCount} 项${changedText ? `,${changedText}` : ''}`)
  324. }
  325. },
  326. async sendStorageAccessSpecialCommand(event) {
  327. const commandKey = event && event.currentTarget && event.currentTarget.dataset
  328. ? event.currentTarget.dataset.command
  329. : ''
  330. try {
  331. const result = await executeStorageAccessSpecialCommand(commandKey || 'reset', this.data)
  332. if (!result.ok) {
  333. if (this.pageToast) this.pageToast.show(result.errorText || '指令执行失败', 'error')
  334. return
  335. }
  336. if (this.pageToast) this.pageToast.show(`${result.command && result.command.label ? result.command.label : '特殊指令'}已执行`)
  337. } catch (error) {
  338. if (this.pageToast) this.pageToast.show(error.message || '指令执行失败', 'error')
  339. }
  340. }
  341. })