const { isCancelError, loadGroupsFromMessageFile, saveGroupsToChat } = require('./persistence.js') const { completeStructInstanceGroups, mergeImportedGroups, parseStructDefinition } = require('./imports.js') const storageAccessCodeInfo = require('../storage-access/code-info-sync.js') const parameterGroupIo = require('./io.js') const settingsService = require('../../store/settings-store.js') const store = require('./store.js') const { loadSelectedFile } = require('../../repositories/file.js') const transport = require('../../transport/ble-core.js') const { DATA_TYPE_OPTIONS, MAX_MODBUS_ADDRESS, MAX_STORAGE_ADDRESS, REGISTER_TYPE_OPTIONS, cloneImportedGroup, getDataType, isAddressRangeOverflow, normalizeGroup, normalizeGroupConfig, validateRegisterValue } = require('../../domain/parameter-groups/model.js') const { findGroup, getGroups, getState, init, setGroups, setStorageCodeInfo, switchProtocolMode, subscribe, updateGroups } = store let initialized = false function getActiveProtocolMode() { return settingsService.getState().protocolMode } function isStorageAccessProtocolMode(protocolMode) { return settingsService.isStorageAccessProtocol(protocolMode) } function getGroupStorageAddressWidth(group = {}) { const codeInfoContext = group.codeInfoContext || {} const explicitWidth = group.sourceAddressWidth || group.storageAddressWidth || group.addressWidth || codeInfoContext.storageAddressWidth || codeInfoContext.addressWidth || codeInfoContext.codeInfoAddressWidth const explicitByteLength = group.sourceAddressByteLength || group.addressByteLength || codeInfoContext.sourceAddressByteLength || codeInfoContext.addressByteLength const width = Number(explicitWidth) if (width === 16 || width === 2) return 16 if (width === 32 || width === 4) return 32 const byteLength = Number(explicitByteLength) if (byteLength === 2) return 16 if (byteLength === 4) return 32 const memoryArea = String(group.sourceMemoryArea || '').trim().toUpperCase() if (memoryArea === 'ADDR32' || memoryArea === 'ADDRESS32') return 32 return Math.floor(Number(group.startAddress) || 0) > MAX_MODBUS_ADDRESS ? 32 : 16 } function getGroupStorageAddressMax(group = {}) { return getGroupStorageAddressWidth(group) === 16 ? MAX_MODBUS_ADDRESS : MAX_STORAGE_ADDRESS } function isGroupAddressRangeOverflow(group = {}, protocolMode = getActiveProtocolMode()) { if (!isStorageAccessProtocolMode(protocolMode)) { return isAddressRangeOverflow(group.startAddress, group.quantity) } const startAddress = Math.max(0, Math.floor(Number(group.startAddress) || 0)) const addressSpan = Math.max(1, Math.floor(Number(group.byteLength || group.sourceByteLength || group.quantity) || 1)) const maxAddress = getGroupStorageAddressMax(group) return startAddress + addressSpan - 1 > maxAddress } function getAddressOverflowText(protocolMode = getActiveProtocolMode(), group = {}) { if (isStorageAccessProtocolMode(protocolMode)) { return getGroupStorageAddressMax(group) === MAX_MODBUS_ADDRESS ? '地址范围超出 0xFFFF' : '地址范围超出 0xFFFFFFFF' } return '地址范围超出 0xFFFF' } function isUnconfiguredRegister(register = {}) { return getDataType(register.dataType).key === 'raw' } function getUnconfiguredRegisterName(register = {}, index = 0) { return register.name || `变量 ${index + 1}` } function findUnconfiguredRegister(group = {}) { const registers = Array.isArray(group.registers) ? group.registers : [] const index = registers.findIndex(isUnconfiguredRegister) return index >= 0 ? { index, register: registers[index] } : null } function initParameterGroups() { settingsService.init() init(getActiveProtocolMode()) switchProtocolMode(getActiveProtocolMode(), { notify: false }) if (initialized) return settingsService.subscribe((settingsState) => { switchProtocolMode(settingsState.protocolMode) }) initialized = true } async function importJsonFromMessageFile() { try { const importedGroups = (await loadGroupsFromMessageFile()).map(normalizeGroup) if (!importedGroups.length) throw new Error('JSON 中没有可导入的寄存器组') const merged = mergeImportedGroups(getGroups(), importedGroups) setGroups(merged.groups) return merged.changedCount || 0 } catch (error) { const message = error && error.message ? error.message : '导入参数组配置失败' transport.showCommandAlert('参数组导入', message) return 0 } } function completeStructInstanceGroupsWithStructSource(sourceText, options = {}) { const completed = completeStructInstanceGroups(getGroups(), sourceText, options) if (!completed.completedCount) { throw new Error('没有找到可匹配的结构体实例') } setGroups(completed.groups) return completed } function mergeImportedGroupsIntoState(importedGroups = [], options = {}) { const normalizedGroups = importedGroups.map(cloneImportedGroup).map(normalizeGroup) const merged = mergeImportedGroups(getGroups(), normalizedGroups, options) if (normalizedGroups.length) { setGroups(merged.groups) } return merged } function clearStorageAccessGroups() { setGroups([], { protocolMode: settingsService.PROTOCOL_MODE.STORAGE_ACCESS }) setStorageCodeInfo(null) return true } async function completeStructInstanceGroupsWithStructFile(options = {}) { try { const file = await loadSelectedFile('auto', { encoding: 'utf8', extensionMessage: '请选择 .h 或 .c 结构体/枚举定义文件', extensions: ['h', 'c', 'txt'], fallbackName: 'structs.h' }) return completeStructInstanceGroupsWithStructSource(file.text, options) } catch (error) { const message = error && error.message ? error.message : '结构体/枚举补全失败' transport.showCommandAlert('结构体/枚举补全', message) return { completedCount: 0, skippedCount: 0, structCount: 0, variableCount: 0 } } } async function saveJsonToChat() { try { const result = await saveGroupsToChat(getGroups()) return result && result.count ? result : { count: 0 } } catch (error) { const message = error && error.message ? error.message : '保存参数组配置失败' if (!isCancelError(error)) { transport.showCommandAlert('参数组保存', message) } return { count: 0 } } } async function syncFromStorageAccessCodeInfo(options = {}) { const result = await storageAccessCodeInfo.syncCodeInfo(options) if (!result || !result.ok) return result setStorageCodeInfo(result.codeInfo) const merged = mergeImportedGroupsIntoState(result.importedGroups || [], { preserveExistingRemarks: true, preserveExistingPollEnabled: true, preserveExistingStructLayout: true }) return { ...result, addedGroups: merged.addedGroupCount, addedRegisters: merged.addedRegisterCount, updatedGroups: merged.updatedGroupCount, updatedRegisters: merged.updatedRegisterCount } } function addGroupFromConfig(config = {}) { const protocolMode = getActiveProtocolMode() let groupConfig try { groupConfig = normalizeGroupConfig({ ...(isStorageAccessProtocolMode(protocolMode) ? { addressUnit: 'byte', sourceMemoryArea: config.sourceMemoryArea || 'XDATA' } : {}), ...config }) } catch (error) { transport.showCommandAlert('参数组添加', error.message || '寄存器组配置无效') return null } if (isGroupAddressRangeOverflow(groupConfig, protocolMode)) { transport.showCommandAlert('参数组添加', getAddressOverflowText(protocolMode, groupConfig)) return null } const registers = Array.isArray(config.registers) ? config.registers : [] const group = normalizeGroup({ ...groupConfig, layout: config.layout, ...(registers.length ? { registers } : {}), expanded: false }) if (group.addressOverflow) { transport.showCommandAlert('参数组添加', getAddressOverflowText(protocolMode, group)) return null } setGroups(getGroups().concat(group)) return group } function updateGroupConfig(groupId, config = {}) { const protocolMode = getActiveProtocolMode() const group = findGroup(groupId) if (!group) return null let nextConfig try { nextConfig = normalizeGroupConfig({ ...group, ...config }) } catch (error) { transport.showCommandAlert('参数组更新', error.message || '寄存器组配置无效') return null } if (isGroupAddressRangeOverflow(nextConfig, protocolMode)) { transport.showCommandAlert('参数组更新', getAddressOverflowText(protocolMode, nextConfig)) return null } const registers = Array.isArray(config.registers) ? config.registers : group.registers const updatedGroup = normalizeGroup({ ...group, ...nextConfig, registers }) if (updatedGroup.addressOverflow) { transport.showCommandAlert('参数组更新', getAddressOverflowText(protocolMode, updatedGroup)) return null } setGroups(getGroups().map((item) => ( item.id === groupId ? updatedGroup : item ))) return updatedGroup } function setGroupExpanded(groupId, expanded) { updateGroups((group) => group.id === groupId ? { ...group, deleteVisible: false, expanded } : group) } function setGroupDeleteVisible(groupId, deleteVisible) { updateGroups((group) => group.id === groupId ? { ...group, deleteVisible } : group) } function removeGroup(groupId) { setGroups(getGroups().filter((group) => group.id !== groupId)) } function reorderRegister(groupId, fromIndex, toIndex) { const group = findGroup(groupId) if (!group) return null if (group.isStructLayout) return group const registers = group.registers.slice() const sourceIndex = Number(fromIndex) const targetIndex = Number(toIndex) if (!Number.isInteger(sourceIndex) || !Number.isInteger(targetIndex)) return null if (sourceIndex < 0 || sourceIndex >= registers.length) return null const safeTargetIndex = Math.min(Math.max(targetIndex, 0), registers.length - 1) if (safeTargetIndex === sourceIndex) return group const moved = registers.splice(sourceIndex, 1)[0] registers.splice(safeTargetIndex, 0, moved) const updatedGroup = normalizeGroup({ ...group, quantity: registers.length, registers }) setGroups(getGroups().map((item) => ( item.id === groupId ? updatedGroup : item ))) return updatedGroup } function updateRegister(groupId, registerIndex, changedData) { updateGroups((group) => { if (group.id !== groupId) return group const shouldResetReadState = Object.prototype.hasOwnProperty.call(changedData, 'dataType') || Object.prototype.hasOwnProperty.call(changedData, 'textByteLength') return { ...group, registers: group.registers.map((register, currentIndex) => ( currentIndex === registerIndex ? { ...register, ...(shouldResetReadState ? { rawBytes: [], rawValue: null, rawWords: [] } : {}), ...changedData } : register )) } }) } function updateRegisterValue(groupId, registerIndex, value) { updateRegister(groupId, registerIndex, { inputValue: value, isDirty: true }) } function validateRegisterInputValue(groupId, registerIndex, value) { const group = findGroup(groupId) if (!group) return false const register = group.registers[registerIndex] if (!register) return false return validateRegisterValue(register, value) } async function readGroup(groupId, options = {}) { const protocolMode = options.protocolMode || getActiveProtocolMode() const group = findGroup(groupId, protocolMode) if (!group) return false if (group.addressOverflow) { transport.showCommandAlert('参数组读取', getAddressOverflowText(protocolMode, group)) return false } const readResult = await parameterGroupIo.readGroup(group, { ...options, useStorageAccess: isStorageAccessProtocolMode(protocolMode) }) if (!readResult) return false updateGroups((item) => { if (item.id !== groupId) return item return readResult.applyToGroup(item) }, { protocolMode }) return true } async function writeRegister(groupId, registerIndex) { const protocolMode = getActiveProtocolMode() const group = findGroup(groupId, protocolMode) const register = group && group.registers ? group.registers[registerIndex] : null if (!group || !register) return false if (!group.writable) { transport.showCommandAlert('参数组写入', '当前变量为只读') return false } if (group.addressOverflow) { transport.showCommandAlert('参数组写入', getAddressOverflowText(protocolMode, group)) return false } if (isUnconfiguredRegister(register)) { transport.showCommandAlert('参数组写入', `${getUnconfiguredRegisterName(register, registerIndex)} 需要先配置数据类型`) return false } const writeResult = await parameterGroupIo.writeRegister(group, registerIndex, { useStorageAccess: isStorageAccessProtocolMode(protocolMode) }) if (!writeResult) return false updateGroups((item) => { if (item.id !== groupId) return item return writeResult.applyToGroup(item) }, { protocolMode }) return true } async function writeGroup(groupId, options = {}) { const protocolMode = options.protocolMode || getActiveProtocolMode() const group = findGroup(groupId, protocolMode) if (!group) return false if (!group.writable) { transport.showCommandAlert('参数组写入', '当前寄存器组为只读') return false } if (group.addressOverflow) { transport.showCommandAlert('参数组写入', getAddressOverflowText(protocolMode, group)) return false } const unconfigured = findUnconfiguredRegister(group) if (unconfigured) { transport.showCommandAlert('参数组写入', `${getUnconfiguredRegisterName(unconfigured.register, unconfigured.index)} 需要先配置数据类型`) return false } const writeResult = await parameterGroupIo.writeGroup(group, { ...options, useStorageAccess: isStorageAccessProtocolMode(protocolMode) }) if (!writeResult) return false updateGroups((item) => { if (item.id !== groupId) return item return writeResult.applyToGroup(item) }, { protocolMode }) return true } module.exports = { DATA_TYPE_OPTIONS, REGISTER_TYPE_OPTIONS, addGroupFromConfig, clearStorageAccessGroups, completeStructInstanceGroups, completeStructInstanceGroupsWithStructFile, completeStructInstanceGroupsWithStructSource, getState, importJsonFromMessageFile, init: initParameterGroups, mergeImportedGroups: mergeImportedGroupsIntoState, parseStructDefinition, readGroup, removeGroup, reorderRegister, saveJsonToChat, setGroupDeleteVisible, setGroupExpanded, subscribe, syncFromStorageAccessCodeInfo, updateGroupConfig, updateRegister, updateRegisterValue, validateRegisterInputValue, writeRegister, writeGroup }