1
0

view-model.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. const parameterGroupService = require('./service.js')
  2. const {
  3. validateValueFormula
  4. } = require('../../domain/parameter-groups/value-formula.js')
  5. const {
  6. normalizeEnumOptions
  7. } = require('../../domain/parameter-groups/value-codec.js')
  8. const settingsService = require('../../store/settings-store.js')
  9. const themeService = require('../../store/theme-store.js')
  10. const transport = require('../../transport/ble-core.js')
  11. function getOption(options, index) {
  12. return options[Number(index)] || options[0] || {}
  13. }
  14. function getCodeInfoVariableByteLength(dialog = {}) {
  15. const entryKind = String(dialog.sourceEntryKind || '').trim().toLowerCase()
  16. const byteLength = Number(dialog.sourceByteLength)
  17. if ((entryKind !== 'variable' && entryKind !== 'enum') || !Number.isFinite(byteLength) || byteLength <= 0) return 0
  18. return Math.floor(byteLength)
  19. }
  20. function getDataTypeConfigByteLength(dataType = {}, dialog = {}) {
  21. if (dataType.kind === 'raw') return getCodeInfoVariableByteLength(dialog)
  22. if (dataType.kind === 'text') return Number(dialog.textByteLength) || Number(dataType.byteLength) || 0
  23. return Number(dataType.byteLength) || Number(dataType.wordCount || 0) * 2
  24. }
  25. function validateCodeInfoVariableDataType(dialog = {}, dataType = {}) {
  26. const sourceByteLength = getCodeInfoVariableByteLength(dialog)
  27. if (!sourceByteLength || dataType.kind === 'raw') return
  28. if (dataType.kind === 'text' || dataType.kind === 'hex') {
  29. throw new Error('变量类型请选择整数、浮点或 enum 对应整数类型')
  30. }
  31. const dataTypeByteLength = getDataTypeConfigByteLength(dataType, dialog)
  32. if (dataTypeByteLength !== sourceByteLength) {
  33. throw new Error(`变量长度为 ${sourceByteLength}B,不能选择 ${dataType.label || dataType.key || '该类型'}`)
  34. }
  35. }
  36. function getStorageSourceDisplayText(sourceMemoryArea) {
  37. const text = String(sourceMemoryArea || '').trim().toUpperCase()
  38. if (text === 'XDATA' || text === 'IDATA' || text === 'DATA' || text === 'CODE') {
  39. return text.toLowerCase()
  40. }
  41. return ''
  42. }
  43. function getStorageSourceTypeText(source = {}) {
  44. return String(
  45. source.sourceDefinitionName
  46. || source.sourceSymbolType
  47. || source.sourceValueType
  48. || source.sourceElementType
  49. || source.dataTypeText
  50. || source.dataType
  51. || ''
  52. ).trim()
  53. }
  54. function getStorageRegisterTypeText(register = {}) {
  55. const entryKind = String(register.sourceEntryKind || '').trim().toLowerCase()
  56. const sourceValueType = String(register.sourceValueType || '').trim()
  57. const sourceSymbolType = String(register.sourceSymbolType || '').trim()
  58. const dataTypeText = String(register.dataTypeText || register.dataType || '').trim()
  59. const isStructFieldSource = entryKind === 'struct'
  60. || (entryKind === 'array' && sourceValueType === 'struct')
  61. || sourceValueType === 'struct'
  62. return String(
  63. register.enumName
  64. || (sourceValueType && sourceValueType !== 'struct' ? sourceValueType : '')
  65. || (!isStructFieldSource && sourceSymbolType && sourceSymbolType !== 'struct' ? sourceSymbolType : '')
  66. || dataTypeText
  67. ).trim()
  68. }
  69. function isEnumTypeSource(source = {}) {
  70. const entryKind = String(source.sourceEntryKind || '').trim().toLowerCase()
  71. const valueTypes = [
  72. source.sourceValueType,
  73. source.sourceElementType,
  74. source.sourceSymbolType,
  75. source.sourceDefinitionName
  76. ]
  77. if (entryKind === 'enum') return true
  78. if (String(source.enumName || '').trim()) return true
  79. if (normalizeEnumOptions(source).length) return true
  80. return valueTypes.some((value) => {
  81. const text = String(value || '').trim().toLowerCase()
  82. return text === 'enum' || /^enum\b/.test(text) || /\benum\b/.test(text)
  83. })
  84. }
  85. function getStorageDetailTitle(source = {}) {
  86. const entryKind = String(source.sourceEntryKind || '').trim().toLowerCase()
  87. const valueType = String(source.sourceValueType || source.sourceElementType || source.sourceSymbolType || '').trim().toLowerCase()
  88. if (isEnumTypeSource(source)) return '枚举详情'
  89. if (entryKind === 'array') return valueType === 'struct' ? '结构体数组详情' : '数组详情'
  90. if (entryKind === 'struct' || valueType === 'struct') return '结构体详情'
  91. if (entryKind === 'variable') return '变量详情'
  92. return '存储详情'
  93. }
  94. function createEnumOptionRows(register = {}) {
  95. return normalizeEnumOptions(register).map((option) => ({
  96. text: `${option.label || option.name} = ${Number(option.value)}`
  97. }))
  98. }
  99. function getPageState() {
  100. const settingsState = settingsService.getState()
  101. const transportState = transport.getState()
  102. const isNoProtocol = settingsService.isNoProtocol(settingsState.protocolMode)
  103. const isModbusProtocol = settingsService.isModbusProtocol(settingsState.protocolMode)
  104. const isStorageAccessProtocol = settingsService.isStorageAccessProtocol(settingsState.protocolMode)
  105. return {
  106. ...parameterGroupService.getState(),
  107. ...themeService.getState(),
  108. ...settingsState,
  109. connectedDevice: transportState.connectedDevice,
  110. isNoProtocol,
  111. isModbusProtocol,
  112. isStorageAccessProtocol
  113. }
  114. }
  115. function resolveActiveParamView(currentView, settingsState = settingsService.getState()) {
  116. if (settingsState.parameterCardControlEnabled === false) return 'parameterGroups'
  117. return currentView === 'parameterGroup' ? currentView : 'parameterGroups'
  118. }
  119. function getSettingsPageState(currentData, settingsState) {
  120. const isNoProtocol = settingsService.isNoProtocol(settingsState.protocolMode)
  121. const isModbusProtocol = settingsService.isModbusProtocol(settingsState.protocolMode)
  122. const isStorageAccessProtocol = settingsService.isStorageAccessProtocol(settingsState.protocolMode)
  123. return {
  124. ...settingsState,
  125. activeParamView: resolveActiveParamView(currentData.activeParamView, settingsState),
  126. isNoProtocol,
  127. isModbusProtocol,
  128. isStorageAccessProtocol
  129. }
  130. }
  131. function getVisiblePageState(currentData) {
  132. const settingsState = settingsService.getState()
  133. const transportState = transport.getState()
  134. const isNoProtocol = settingsService.isNoProtocol(settingsState.protocolMode)
  135. const isModbusProtocol = settingsService.isModbusProtocol(settingsState.protocolMode)
  136. const isStorageAccessProtocol = settingsService.isStorageAccessProtocol(settingsState.protocolMode)
  137. const pageState = {
  138. ...parameterGroupService.getState(),
  139. ...themeService.getState(),
  140. ...settingsState,
  141. connectedDevice: transportState.connectedDevice,
  142. isNoProtocol,
  143. isModbusProtocol,
  144. isStorageAccessProtocol
  145. }
  146. return {
  147. ...pageState,
  148. activeParamView: resolveActiveParamView(currentData.activeParamView, settingsState)
  149. }
  150. }
  151. function getTransportPageState(transportState = transport.getState()) {
  152. return {
  153. connectedDevice: transportState.connectedDevice
  154. }
  155. }
  156. function createParameterDialogState(overrides = {}) {
  157. const registerType = getOption(parameterGroupService.REGISTER_TYPE_OPTIONS, 0)
  158. const dataType = getOption(parameterGroupService.DATA_TYPE_OPTIONS, 0)
  159. return {
  160. cancelText: '取消',
  161. confirmText: '确认',
  162. dataTypeIndex: 0,
  163. dataTypeText: dataType.label || '',
  164. groupId: '',
  165. groupName: '',
  166. mode: '',
  167. name: '',
  168. quantity: '1',
  169. registerIndex: -1,
  170. registerTypeIndex: 0,
  171. registerTypeText: registerType.label || '',
  172. remark: '',
  173. startAddress: '0000',
  174. title: '',
  175. textByteLength: '32',
  176. showTextLength: false,
  177. unit: '',
  178. visible: false,
  179. maxValue: '',
  180. minValue: '',
  181. conversionFormula: '',
  182. conversionFormulaErrorText: '',
  183. addressText: '',
  184. displayName: '',
  185. displayValue: '',
  186. enumExpanded: false,
  187. enumOptions: [],
  188. isEnumType: false,
  189. rawValueText: '',
  190. showDataType: false,
  191. showRange: false,
  192. showUnit: false,
  193. readOnly: false,
  194. metadataLocked: false,
  195. parsedStructRegisters: [],
  196. pollEnabled: true,
  197. showPollEnabled: false,
  198. sourceAddressText: '',
  199. sourceByteLength: '',
  200. sourceDefinitionName: '',
  201. sourceDisplayText: '',
  202. sourceEntryKind: '',
  203. sourceElementType: '',
  204. sourceMemoryArea: '',
  205. sourceSymbolName: '',
  206. sourceSymbolType: '',
  207. sourceTypeText: '',
  208. sourceValueType: '',
  209. structDefinition: '',
  210. structParsedSummary: '',
  211. ...overrides
  212. }
  213. }
  214. function createParameterGroupDialogState(group) {
  215. const isEdit = !!group
  216. const isStorageGroup = !!(group && group.sourceMemoryArea)
  217. const metadataLocked = !!(group && group.sourceMetadataLocked)
  218. const firstRegister = isEdit && Array.isArray(group.registers) ? (group.registers[0] || {}) : {}
  219. const registerTypeIndex = isEdit ? (group.registerTypeIndex || 0) : 0
  220. const registerType = getOption(parameterGroupService.REGISTER_TYPE_OPTIONS, registerTypeIndex)
  221. return createParameterDialogState({
  222. confirmText: isEdit ? '保存' : '确认',
  223. displayName: isEdit ? (group.displayName || group.name) : '',
  224. enumOptions: createEnumOptionRows(firstRegister),
  225. groupId: isEdit ? group.id : '',
  226. groupName: isEdit ? group.name : (isStorageGroup ? '结构体组' : '寄存器组'),
  227. layout: isEdit ? (group.layout || 'register') : 'register',
  228. metadataLocked,
  229. mode: isEdit ? 'editGroup' : 'createGroup',
  230. quantity: isEdit ? String(group.quantity || 1) : '1',
  231. pollEnabled: isEdit ? group.pollEnabled !== false : true,
  232. registerTypeIndex,
  233. registerTypeText: registerType.label || '',
  234. showPollEnabled: true,
  235. sourceAddressText: isEdit ? (group.sourceAddressText || group.startAddressText || '') : '',
  236. sourceByteLength: isEdit ? (group.sourceByteLength || group.byteLength || '') : '',
  237. sourceDefinitionName: isEdit ? (group.sourceDefinitionName || '') : '',
  238. sourceDisplayText: isEdit ? getStorageSourceDisplayText(group.sourceMemoryArea) : '',
  239. sourceEntryKind: isEdit ? (group.sourceEntryKind || '') : '',
  240. sourceElementType: isEdit ? (group.sourceElementType || '') : '',
  241. sourceMemoryArea: isEdit ? (group.sourceMemoryArea || '') : '',
  242. sourceMetaText: isEdit ? (group.sourceMetaText || '') : '',
  243. sourceSymbolName: isEdit ? (group.sourceSymbolName || '') : '',
  244. sourceSymbolType: isEdit ? (group.sourceSymbolType || '') : '',
  245. sourceTypeText: isEdit ? getStorageSourceTypeText(group) : '',
  246. sourceValueType: isEdit ? (group.sourceValueType || '') : '',
  247. startAddress: isEdit && group.startAddressText ? group.startAddressText.replace(/^0x/i, '') : '0000',
  248. title: metadataLocked
  249. ? getStorageDetailTitle(group)
  250. : (isStorageGroup
  251. ? (isEdit ? '编辑结构体组' : '添加结构体组')
  252. : (isEdit ? '编辑寄存器组' : '添加寄存器组')),
  253. visible: true
  254. })
  255. }
  256. function createParameterRegisterDialogState(mode, group, register, registerIndex) {
  257. const isView = mode === 'viewRegister'
  258. const metadataLocked = !!(register && register.sourceMetadataLocked) || !!(group && group.sourceMetadataLocked)
  259. const dataTypeIndex = register.dataTypeIndex || 0
  260. const dataType = getOption(parameterGroupService.DATA_TYPE_OPTIONS, dataTypeIndex)
  261. const isEnumType = isEnumTypeSource(register)
  262. || isEnumTypeSource(group)
  263. || !!(group && group.isStorageEnumScalar)
  264. return createParameterDialogState({
  265. cancelText: isView ? '关闭' : '取消',
  266. confirmText: isView ? '' : '保存',
  267. dataTypeIndex,
  268. dataTypeText: register.dataTypeText || dataType.label || '',
  269. displayName: register.displayName || register.name,
  270. groupId: group.id,
  271. groupName: group.name,
  272. metadataLocked,
  273. mode,
  274. name: register.name,
  275. registerIndex,
  276. registerTypeIndex: group.registerTypeIndex || 0,
  277. remark: register.remark || '',
  278. startAddress: group.startAddressText ? group.startAddressText.replace(/^0x/i, '') : '0000',
  279. textByteLength: String(register.textByteLength || '32'),
  280. showTextLength: !!register.showTextLength,
  281. unit: register.unit || '',
  282. visible: true,
  283. conversionFormula: isEnumType ? '' : (register.conversionFormula || ''),
  284. conversionFormulaErrorText: isEnumType ? '' : (register.conversionFormulaErrorText || ''),
  285. maxValue: isEnumType ? '' : (register.maxValue || ''),
  286. minValue: isEnumType ? '' : (register.minValue || ''),
  287. enumOptions: createEnumOptionRows(register),
  288. isEnumType,
  289. addressText: register.addressRangeText || register.addressText || '',
  290. displayValue: register.displayValue || '',
  291. rawValueText: register.rawValueText || '--',
  292. sourceAddressText: register.sourceAddressText || register.addressText || '',
  293. sourceByteLength: register.sourceByteLength || '',
  294. sourceDefinitionName: register.sourceDefinitionName || '',
  295. sourceDisplayText: getStorageSourceDisplayText(register.sourceMemoryArea || group.sourceMemoryArea),
  296. sourceEntryKind: register.sourceEntryKind || '',
  297. sourceElementType: register.sourceElementType || '',
  298. sourceMemoryArea: register.sourceMemoryArea || group.sourceMemoryArea || '',
  299. sourceMetaText: register.sourceMetaText || '',
  300. sourceSymbolName: register.sourceSymbolName || '',
  301. sourceSymbolType: register.sourceSymbolType || '',
  302. sourceTypeText: getStorageRegisterTypeText(register),
  303. sourceValueType: register.sourceValueType || '',
  304. showDataType: !!register.showDataType,
  305. showRange: !isEnumType && !!register.showRange,
  306. showPollEnabled: metadataLocked && isEnumType,
  307. showUnit: !isEnumType && !!register.showUnit,
  308. pollEnabled: group.pollEnabled !== false,
  309. readOnly: isView,
  310. title: metadataLocked ? getStorageDetailTitle(register) : (isView ? '寄存器信息' : '寄存器配置')
  311. })
  312. }
  313. function getDialogDataTypeState(dialog, dataTypeOptions, dataTypeIndex) {
  314. const dataType = getOption(dataTypeOptions, dataTypeIndex)
  315. const isTextType = dataType.kind === 'text'
  316. const showUnit = dataType.kind === 'number' && dataType.key !== 'hex'
  317. return {
  318. dataTypeIndex,
  319. dataTypeText: dataType.label || '',
  320. maxValue: isTextType ? '' : dialog.maxValue,
  321. minValue: isTextType ? '' : dialog.minValue,
  322. showRange: !isTextType,
  323. showTextLength: isTextType,
  324. showUnit,
  325. textByteLength: isTextType ? (dialog.textByteLength || '32') : dialog.textByteLength,
  326. unit: showUnit ? dialog.unit : ''
  327. }
  328. }
  329. function createParameterGroupConfig(dialog) {
  330. const registers = Array.isArray(dialog.parsedStructRegisters)
  331. ? dialog.parsedStructRegisters
  332. : []
  333. return {
  334. groupName: dialog.groupName,
  335. layout: registers.length ? 'struct' : (dialog.layout || 'register'),
  336. pollEnabled: dialog.pollEnabled !== false,
  337. quantity: registers.length ? String(registers.length) : dialog.quantity,
  338. registerTypeIndex: dialog.registerTypeIndex,
  339. startAddress: dialog.startAddress,
  340. ...(registers.length ? {
  341. registers: registers.map((register) => ({
  342. bitOffset: register.bitOffset,
  343. bitWidth: register.bitWidth,
  344. byteStart: register.byteStart,
  345. dataType: register.dataType,
  346. isBitField: !!register.isBitField,
  347. isStructField: true,
  348. name: register.name,
  349. structByteLength: register.structByteLength,
  350. textByteLength: register.textByteLength
  351. }))
  352. } : {})
  353. }
  354. }
  355. function createParameterRegisterChangedData(dialog, dataTypeOptions) {
  356. const dataType = getOption(dataTypeOptions, dialog.dataTypeIndex)
  357. const isTextType = dataType.kind === 'text'
  358. const isEnumType = !!dialog.isEnumType
  359. const showUnit = !isEnumType && dataType.kind === 'number' && dataType.key !== 'hex'
  360. const conversionFormula = isEnumType ? '' : String(dialog.conversionFormula || '').trim()
  361. validateValueFormula(conversionFormula)
  362. const changedData = {
  363. conversionFormula,
  364. maxValue: isTextType || isEnumType ? '' : dialog.maxValue,
  365. minValue: isTextType || isEnumType ? '' : dialog.minValue,
  366. remark: dialog.remark,
  367. unit: showUnit ? dialog.unit : ''
  368. }
  369. if (dialog.metadataLocked) return changedData
  370. validateCodeInfoVariableDataType(dialog, dataType)
  371. return {
  372. ...changedData,
  373. name: dialog.name,
  374. dataType: dataType.key,
  375. textByteLength: isTextType ? dialog.textByteLength : ''
  376. }
  377. }
  378. function findParameterGroup(groups, groupId) {
  379. return (groups || []).find((item) => item.id === groupId) || null
  380. }
  381. function findParameterRegister(groups, groupId, registerIndex) {
  382. const group = findParameterGroup(groups, groupId)
  383. const register = group && group.registers ? group.registers[registerIndex] : null
  384. return {
  385. group,
  386. register
  387. }
  388. }
  389. function getActiveParameterGroup(groups, groupId) {
  390. return findParameterGroup(groups, groupId) || null
  391. }
  392. module.exports = {
  393. createParameterGroupConfig,
  394. createParameterGroupDialogState,
  395. createParameterDialogState,
  396. createParameterRegisterChangedData,
  397. createParameterRegisterDialogState,
  398. findParameterGroup,
  399. findParameterRegister,
  400. getActiveParameterGroup,
  401. getDialogDataTypeState,
  402. getOption,
  403. getPageState,
  404. getSettingsPageState,
  405. getTransportPageState,
  406. getVisiblePageState,
  407. resolveActiveParamView
  408. }