const { isCancelError, loadGroupsFromMessageFile, saveGroupsToChat } = require('./persistence.js') const { mergeImportedGroups } = require('./import-merge.js') const { completeStructInstanceGroups, parseStructDefinition } = require('./struct-completion.js') const storageAccessService = require('../storage-access/service.js') const storageAccessIo = require('./storage-access-io.js') const modbusIo = require('./modbus-io.js') const settingsService = require('../../store/settings-store.js') const store = require('./store.js') const stateMappers = require('./state-mappers.js') const { loadSelectedFile } = require('../../repositories/file.js') const transport = require('../../transport/ble-core.js') const { DATA_TYPE_OPTIONS, REGISTER_TYPE_OPTIONS, cloneImportedGroup, 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 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 } async function completeStructInstanceGroupsWithStructFile(options = {}) { try { const file = await loadSelectedFile('message', { 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 { return saveGroupsToChat(getGroups()) } catch (error) { const message = error && error.message ? error.message : '保存参数组配置失败' if (!isCancelError(error)) { transport.showCommandAlert('参数组保存', message) } return 0 } } async function syncFromStorageAccessCodeInfo(options = {}) { const result = await storageAccessService.syncCodeInfo(options) if (!result || !result.ok) return result setStorageCodeInfo(result.codeInfo) const merged = mergeImportedGroupsIntoState(result.importedGroups || [], { preserveExistingRemarks: true, preserveExistingStructLayout: true }) return { ...result, addedGroups: merged.addedGroupCount, addedRegisters: merged.addedRegisterCount, updatedGroups: merged.updatedGroupCount, updatedRegisters: merged.updatedRegisterCount } } function addGroupFromConfig(config = {}) { let groupConfig try { groupConfig = normalizeGroupConfig(config) } catch (error) { transport.showCommandAlert('参数组添加', error.message || '寄存器组配置无效') return null } if (isAddressRangeOverflow(groupConfig.startAddress, groupConfig.quantity)) { transport.showCommandAlert('参数组添加', '地址范围超出 0xFFFF') 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('参数组添加', '地址范围超出 0xFFFF') return null } setGroups(getGroups().concat(group)) return group } function updateGroupConfig(groupId, config = {}) { const group = findGroup(groupId) if (!group) return null let nextConfig try { nextConfig = normalizeGroupConfig({ ...group, ...config }) } catch (error) { transport.showCommandAlert('参数组更新', error.message || '寄存器组配置无效') return null } if (isAddressRangeOverflow(nextConfig.startAddress, nextConfig.quantity)) { transport.showCommandAlert('参数组更新', '地址范围超出 0xFFFF') return null } const registers = Array.isArray(config.registers) ? config.registers : group.registers const updatedGroup = normalizeGroup({ ...group, ...nextConfig, registers }) if (updatedGroup.addressOverflow) { transport.showCommandAlert('参数组更新', '地址范围超出 0xFFFF') 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 ? { 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('参数组读取', '地址范围超出 0xFFFF') return false } let wordCache = {} if (isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)) { const memoryWordCache = await storageAccessIo.readMemoryGroup(group, options) if (!memoryWordCache) return false wordCache = stateMappers.normalizeNumericCache(memoryWordCache) } else { const modbusWordCache = await modbusIo.readGroup(group, options) if (!modbusWordCache) return false wordCache = stateMappers.normalizeNumericCache(modbusWordCache) } updateGroups((item) => { if (item.id !== groupId) return item return stateMappers.applyReadCacheToGroup(item, wordCache) }, { 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('参数组写入', '地址范围超出 0xFFFF') return false } const written = isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group) ? await storageAccessIo.writeMemoryRegister(group, register) : null if (!written) { if (!isStorageAccessProtocolMode(protocolMode) || !storageAccessIo.isMemoryGroup(group)) { return writeGroup(groupId, { protocolMode }) } return false } updateGroups((item) => { if (item.id !== groupId) return item return stateMappers.applyWrittenSnapshotToGroupRegister(item, registerIndex, written) }, { 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('参数组写入', '地址范围超出 0xFFFF') return false } const writtenRegisters = [] if (isStorageAccessProtocolMode(protocolMode) && storageAccessIo.isMemoryGroup(group)) { const snapshots = await storageAccessIo.writeMemoryGroup(group) if (!snapshots) return false snapshots.forEach((snapshot) => { writtenRegisters.push(snapshot) }) } else { const snapshots = await modbusIo.writeGroup(group, options) if (!snapshots) return false snapshots.forEach((snapshot) => { writtenRegisters.push(snapshot) }) } updateGroups((item) => { if (item.id !== groupId) return item return stateMappers.applyWrittenSnapshotsToGroup(item, writtenRegisters) }, { protocolMode }) return true } module.exports = { DATA_TYPE_OPTIONS, REGISTER_TYPE_OPTIONS, addGroupFromConfig, completeStructInstanceGroups, completeStructInstanceGroupsWithStructFile, completeStructInstanceGroupsWithStructSource, getState, importJsonFromMessageFile, init: initParameterGroups, mergeImportedGroups: mergeImportedGroupsIntoState, parseStructDefinition, readGroup, removeGroup, reorderRegister, saveJsonToChat, setGroupDeleteVisible, setGroupExpanded, subscribe, syncFromStorageAccessCodeInfo, updateGroupConfig, updateRegister, updateRegisterValue, validateRegisterInputValue, writeRegister, writeGroup }