const parameterGroupService = require('./service.js') const { validateValueFormula } = require('../../domain/parameter-groups/value-formula.js') const { normalizeEnumOptions } = require('../../domain/parameter-groups/value-codec.js') const settingsService = require('../../store/settings-store.js') const themeService = require('../../store/theme-store.js') const transport = require('../../transport/ble-core.js') function getOption(options, index) { return options[Number(index)] || options[0] || {} } function getCodeInfoVariableByteLength(dialog = {}) { const entryKind = String(dialog.sourceEntryKind || '').trim().toLowerCase() const byteLength = Number(dialog.sourceByteLength) if ((entryKind !== 'variable' && entryKind !== 'enum') || !Number.isFinite(byteLength) || byteLength <= 0) return 0 return Math.floor(byteLength) } function getDataTypeConfigByteLength(dataType = {}, dialog = {}) { if (dataType.kind === 'raw') return getCodeInfoVariableByteLength(dialog) if (dataType.kind === 'text') return Number(dialog.textByteLength) || Number(dataType.byteLength) || 0 return Number(dataType.byteLength) || Number(dataType.wordCount || 0) * 2 } function validateCodeInfoVariableDataType(dialog = {}, dataType = {}) { const sourceByteLength = getCodeInfoVariableByteLength(dialog) if (!sourceByteLength || dataType.kind === 'raw') return if (dataType.kind === 'text' || dataType.kind === 'hex') { throw new Error('变量类型请选择整数、浮点或 enum 对应整数类型') } const dataTypeByteLength = getDataTypeConfigByteLength(dataType, dialog) if (dataTypeByteLength !== sourceByteLength) { throw new Error(`变量长度为 ${sourceByteLength}B,不能选择 ${dataType.label || dataType.key || '该类型'}`) } } function getStorageSourceDisplayText(sourceMemoryArea) { const text = String(sourceMemoryArea || '').trim().toUpperCase() if (text === 'XDATA' || text === 'IDATA' || text === 'DATA' || text === 'CODE') { return text.toLowerCase() } return '' } function getStorageSourceTypeText(source = {}) { return String( source.sourceDefinitionName || source.sourceSymbolType || source.sourceValueType || source.sourceElementType || source.dataTypeText || source.dataType || '' ).trim() } function getStorageRegisterTypeText(register = {}) { const entryKind = String(register.sourceEntryKind || '').trim().toLowerCase() const sourceValueType = String(register.sourceValueType || '').trim() const sourceSymbolType = String(register.sourceSymbolType || '').trim() const dataTypeText = String(register.dataTypeText || register.dataType || '').trim() const isStructFieldSource = entryKind === 'struct' || (entryKind === 'array' && sourceValueType === 'struct') || sourceValueType === 'struct' return String( register.enumName || (sourceValueType && sourceValueType !== 'struct' ? sourceValueType : '') || (!isStructFieldSource && sourceSymbolType && sourceSymbolType !== 'struct' ? sourceSymbolType : '') || dataTypeText ).trim() } function isEnumTypeSource(source = {}) { const entryKind = String(source.sourceEntryKind || '').trim().toLowerCase() const valueTypes = [ source.sourceValueType, source.sourceElementType, source.sourceSymbolType, source.sourceDefinitionName ] if (entryKind === 'enum') return true if (String(source.enumName || '').trim()) return true if (normalizeEnumOptions(source).length) return true return valueTypes.some((value) => { const text = String(value || '').trim().toLowerCase() return text === 'enum' || /^enum\b/.test(text) || /\benum\b/.test(text) }) } function getStorageDetailTitle(source = {}) { const entryKind = String(source.sourceEntryKind || '').trim().toLowerCase() const valueType = String(source.sourceValueType || source.sourceElementType || source.sourceSymbolType || '').trim().toLowerCase() if (isEnumTypeSource(source)) return '枚举详情' if (entryKind === 'array') return valueType === 'struct' ? '结构体数组详情' : '数组详情' if (entryKind === 'struct' || valueType === 'struct') return '结构体详情' if (entryKind === 'variable') return '变量详情' return '存储详情' } function createEnumOptionRows(register = {}) { return normalizeEnumOptions(register).map((option) => ({ text: `${option.label || option.name} = ${Number(option.value)}` })) } function getPageState() { const settingsState = settingsService.getState() const transportState = transport.getState() const isNoProtocol = settingsService.isNoProtocol(settingsState.protocolMode) const isModbusProtocol = settingsService.isModbusProtocol(settingsState.protocolMode) const isStorageAccessProtocol = settingsService.isStorageAccessProtocol(settingsState.protocolMode) return { ...parameterGroupService.getState(), ...themeService.getState(), ...settingsState, connectedDevice: transportState.connectedDevice, isNoProtocol, isModbusProtocol, isStorageAccessProtocol } } function resolveActiveParamView(currentView, settingsState = settingsService.getState()) { if (settingsState.parameterCardControlEnabled === false) return 'parameterGroups' return currentView === 'parameterGroup' ? currentView : 'parameterGroups' } function getSettingsPageState(currentData, settingsState) { const isNoProtocol = settingsService.isNoProtocol(settingsState.protocolMode) const isModbusProtocol = settingsService.isModbusProtocol(settingsState.protocolMode) const isStorageAccessProtocol = settingsService.isStorageAccessProtocol(settingsState.protocolMode) return { ...settingsState, activeParamView: resolveActiveParamView(currentData.activeParamView, settingsState), isNoProtocol, isModbusProtocol, isStorageAccessProtocol } } function getVisiblePageState(currentData) { const settingsState = settingsService.getState() const transportState = transport.getState() const isNoProtocol = settingsService.isNoProtocol(settingsState.protocolMode) const isModbusProtocol = settingsService.isModbusProtocol(settingsState.protocolMode) const isStorageAccessProtocol = settingsService.isStorageAccessProtocol(settingsState.protocolMode) const pageState = { ...parameterGroupService.getState(), ...themeService.getState(), ...settingsState, connectedDevice: transportState.connectedDevice, isNoProtocol, isModbusProtocol, isStorageAccessProtocol } return { ...pageState, activeParamView: resolveActiveParamView(currentData.activeParamView, settingsState) } } function getTransportPageState(transportState = transport.getState()) { return { connectedDevice: transportState.connectedDevice } } function createParameterDialogState(overrides = {}) { const registerType = getOption(parameterGroupService.REGISTER_TYPE_OPTIONS, 0) const dataType = getOption(parameterGroupService.DATA_TYPE_OPTIONS, 0) return { cancelText: '取消', confirmText: '确认', dataTypeIndex: 0, dataTypeText: dataType.label || '', groupId: '', groupName: '', mode: '', name: '', quantity: '1', registerIndex: -1, registerTypeIndex: 0, registerTypeText: registerType.label || '', remark: '', startAddress: '0000', title: '', textByteLength: '32', showTextLength: false, unit: '', visible: false, maxValue: '', minValue: '', conversionFormula: '', conversionFormulaErrorText: '', addressText: '', displayName: '', displayValue: '', enumExpanded: false, enumOptions: [], isEnumType: false, rawValueText: '', showDataType: false, showRange: false, showUnit: false, readOnly: false, metadataLocked: false, parsedStructRegisters: [], pollEnabled: true, showPollEnabled: false, sourceAddressText: '', sourceByteLength: '', sourceDefinitionName: '', sourceDisplayText: '', sourceEntryKind: '', sourceElementType: '', sourceMemoryArea: '', sourceSymbolName: '', sourceSymbolType: '', sourceTypeText: '', sourceValueType: '', structDefinition: '', structParsedSummary: '', ...overrides } } function createParameterGroupDialogState(group) { const isEdit = !!group const isStorageGroup = !!(group && group.sourceMemoryArea) const metadataLocked = !!(group && group.sourceMetadataLocked) const firstRegister = isEdit && Array.isArray(group.registers) ? (group.registers[0] || {}) : {} const registerTypeIndex = isEdit ? (group.registerTypeIndex || 0) : 0 const registerType = getOption(parameterGroupService.REGISTER_TYPE_OPTIONS, registerTypeIndex) return createParameterDialogState({ confirmText: isEdit ? '保存' : '确认', displayName: isEdit ? (group.displayName || group.name) : '', enumOptions: createEnumOptionRows(firstRegister), groupId: isEdit ? group.id : '', groupName: isEdit ? group.name : (isStorageGroup ? '结构体组' : '寄存器组'), layout: isEdit ? (group.layout || 'register') : 'register', metadataLocked, mode: isEdit ? 'editGroup' : 'createGroup', quantity: isEdit ? String(group.quantity || 1) : '1', pollEnabled: isEdit ? group.pollEnabled !== false : true, registerTypeIndex, registerTypeText: registerType.label || '', showPollEnabled: true, sourceAddressText: isEdit ? (group.sourceAddressText || group.startAddressText || '') : '', sourceByteLength: isEdit ? (group.sourceByteLength || group.byteLength || '') : '', sourceDefinitionName: isEdit ? (group.sourceDefinitionName || '') : '', sourceDisplayText: isEdit ? getStorageSourceDisplayText(group.sourceMemoryArea) : '', sourceEntryKind: isEdit ? (group.sourceEntryKind || '') : '', sourceElementType: isEdit ? (group.sourceElementType || '') : '', sourceMemoryArea: isEdit ? (group.sourceMemoryArea || '') : '', sourceMetaText: isEdit ? (group.sourceMetaText || '') : '', sourceSymbolName: isEdit ? (group.sourceSymbolName || '') : '', sourceSymbolType: isEdit ? (group.sourceSymbolType || '') : '', sourceTypeText: isEdit ? getStorageSourceTypeText(group) : '', sourceValueType: isEdit ? (group.sourceValueType || '') : '', startAddress: isEdit && group.startAddressText ? group.startAddressText.replace(/^0x/i, '') : '0000', title: metadataLocked ? getStorageDetailTitle(group) : (isStorageGroup ? (isEdit ? '编辑结构体组' : '添加结构体组') : (isEdit ? '编辑寄存器组' : '添加寄存器组')), visible: true }) } function createParameterRegisterDialogState(mode, group, register, registerIndex) { const isView = mode === 'viewRegister' const metadataLocked = !!(register && register.sourceMetadataLocked) || !!(group && group.sourceMetadataLocked) const dataTypeIndex = register.dataTypeIndex || 0 const dataType = getOption(parameterGroupService.DATA_TYPE_OPTIONS, dataTypeIndex) const isEnumType = isEnumTypeSource(register) || isEnumTypeSource(group) || !!(group && group.isStorageEnumScalar) return createParameterDialogState({ cancelText: isView ? '关闭' : '取消', confirmText: isView ? '' : '保存', dataTypeIndex, dataTypeText: register.dataTypeText || dataType.label || '', displayName: register.displayName || register.name, groupId: group.id, groupName: group.name, metadataLocked, mode, name: register.name, registerIndex, registerTypeIndex: group.registerTypeIndex || 0, remark: register.remark || '', startAddress: group.startAddressText ? group.startAddressText.replace(/^0x/i, '') : '0000', textByteLength: String(register.textByteLength || '32'), showTextLength: !!register.showTextLength, unit: register.unit || '', visible: true, conversionFormula: isEnumType ? '' : (register.conversionFormula || ''), conversionFormulaErrorText: isEnumType ? '' : (register.conversionFormulaErrorText || ''), maxValue: isEnumType ? '' : (register.maxValue || ''), minValue: isEnumType ? '' : (register.minValue || ''), enumOptions: createEnumOptionRows(register), isEnumType, addressText: register.addressRangeText || register.addressText || '', displayValue: register.displayValue || '', rawValueText: register.rawValueText || '--', sourceAddressText: register.sourceAddressText || register.addressText || '', sourceByteLength: register.sourceByteLength || '', sourceDefinitionName: register.sourceDefinitionName || '', sourceDisplayText: getStorageSourceDisplayText(register.sourceMemoryArea || group.sourceMemoryArea), sourceEntryKind: register.sourceEntryKind || '', sourceElementType: register.sourceElementType || '', sourceMemoryArea: register.sourceMemoryArea || group.sourceMemoryArea || '', sourceMetaText: register.sourceMetaText || '', sourceSymbolName: register.sourceSymbolName || '', sourceSymbolType: register.sourceSymbolType || '', sourceTypeText: getStorageRegisterTypeText(register), sourceValueType: register.sourceValueType || '', showDataType: !!register.showDataType, showRange: !isEnumType && !!register.showRange, showPollEnabled: metadataLocked && isEnumType, showUnit: !isEnumType && !!register.showUnit, pollEnabled: group.pollEnabled !== false, readOnly: isView, title: metadataLocked ? getStorageDetailTitle(register) : (isView ? '寄存器信息' : '寄存器配置') }) } function getDialogDataTypeState(dialog, dataTypeOptions, dataTypeIndex) { const dataType = getOption(dataTypeOptions, dataTypeIndex) const isTextType = dataType.kind === 'text' const showUnit = dataType.kind === 'number' && dataType.key !== 'hex' return { dataTypeIndex, dataTypeText: dataType.label || '', maxValue: isTextType ? '' : dialog.maxValue, minValue: isTextType ? '' : dialog.minValue, showRange: !isTextType, showTextLength: isTextType, showUnit, textByteLength: isTextType ? (dialog.textByteLength || '32') : dialog.textByteLength, unit: showUnit ? dialog.unit : '' } } function createParameterGroupConfig(dialog) { const registers = Array.isArray(dialog.parsedStructRegisters) ? dialog.parsedStructRegisters : [] return { groupName: dialog.groupName, layout: registers.length ? 'struct' : (dialog.layout || 'register'), pollEnabled: dialog.pollEnabled !== false, quantity: registers.length ? String(registers.length) : dialog.quantity, registerTypeIndex: dialog.registerTypeIndex, startAddress: dialog.startAddress, ...(registers.length ? { registers: registers.map((register) => ({ bitOffset: register.bitOffset, bitWidth: register.bitWidth, byteStart: register.byteStart, dataType: register.dataType, isBitField: !!register.isBitField, isStructField: true, name: register.name, structByteLength: register.structByteLength, textByteLength: register.textByteLength })) } : {}) } } function createParameterRegisterChangedData(dialog, dataTypeOptions) { const dataType = getOption(dataTypeOptions, dialog.dataTypeIndex) const isTextType = dataType.kind === 'text' const isEnumType = !!dialog.isEnumType const showUnit = !isEnumType && dataType.kind === 'number' && dataType.key !== 'hex' const conversionFormula = isEnumType ? '' : String(dialog.conversionFormula || '').trim() validateValueFormula(conversionFormula) const changedData = { conversionFormula, maxValue: isTextType || isEnumType ? '' : dialog.maxValue, minValue: isTextType || isEnumType ? '' : dialog.minValue, remark: dialog.remark, unit: showUnit ? dialog.unit : '' } if (dialog.metadataLocked) return changedData validateCodeInfoVariableDataType(dialog, dataType) return { ...changedData, name: dialog.name, dataType: dataType.key, textByteLength: isTextType ? dialog.textByteLength : '' } } function findParameterGroup(groups, groupId) { return (groups || []).find((item) => item.id === groupId) || null } function findParameterRegister(groups, groupId, registerIndex) { const group = findParameterGroup(groups, groupId) const register = group && group.registers ? group.registers[registerIndex] : null return { group, register } } function getActiveParameterGroup(groups, groupId) { return findParameterGroup(groups, groupId) || null } module.exports = { createParameterGroupConfig, createParameterGroupDialogState, createParameterDialogState, createParameterRegisterChangedData, createParameterRegisterDialogState, findParameterGroup, findParameterRegister, getActiveParameterGroup, getDialogDataTypeState, getOption, getPageState, getSettingsPageState, getTransportPageState, getVisiblePageState, resolveActiveParamView }