const { formatExportStamp, isCancelError, loadSelectedFile, saveTextFileToChat } = require('../../repositories/file.js') const { getWxApi } = require('../../utils/platform-utils.js') const { cloneImportedGroup, getRegisterJsonValue } = require('../../domain/parameter-groups/model.js') const STORAGE_KEY = 'parameter-groups-json' const STORAGE_MIGRATION_KEY = 'parameter-groups-json-protocol-migration' const JSON_DOCUMENT_TYPE = 'parameter-groups' const JSON_SCHEMA_VERSION = 2 const PROTOCOL_MODE = { MODBUS_RTU: 'modbus-rtu', STORAGE_ACCESS: 'storage-access' } const GROUP_SOURCE_FIELDS = [ 'addressUnit', 'sourceAddress', 'sourceAddressText', 'sourceByteLength', 'sourceMemoryArea', 'sourceMemoryClass', 'sourceSegment', 'sourceSegmentModule', 'sourceSymbolName' ] const REGISTER_SOURCE_FIELDS = [ 'conversionFormula', 'sourceAddress', 'sourceAddressText', 'sourceByteLength', 'sourceBitOffset', 'sourceBitWidth', 'sourceMemoryArea', 'sourceMemoryClass', 'sourceSymbolName', 'sourceSymbolType' ] const REGISTER_STRUCT_FIELDS = [ 'bitOffset', 'bitWidth', 'byteStart', 'isPlaceholderByteField', 'isBitField', 'structByteLength' ] function pickFields(source, fields) { return fields.reduce((result, field) => { if (source && source[field] !== undefined && source[field] !== null && source[field] !== '') { result[field] = source[field] } return result }, {}) } function toPersistedGroups(groups = []) { return groups.map((group) => ({ layout: group.layout, name: group.name, registerType: group.registerType, startAddress: group.startAddress, quantity: group.quantity, registers: group.registers.map((register) => ({ dataType: register.dataType, defaultValue: register.defaultValue, conversionFormula: register.conversionFormula, isStructField: register.isStructField, name: register.name, maxValue: register.maxValue, minValue: register.minValue, textByteLength: register.textByteLength, remark: register.remark, unit: register.unit, value: getRegisterJsonValue(register), ...pickFields(register, REGISTER_STRUCT_FIELDS), ...pickFields(register, REGISTER_SOURCE_FIELDS) })), ...pickFields(group, GROUP_SOURCE_FIELDS) })) } function toJsonData(groups = [], options = {}) { const jsonData = { groups: toPersistedGroups(groups), type: JSON_DOCUMENT_TYPE, version: JSON_SCHEMA_VERSION } if (options.includeExportedAt) { jsonData.exportedAt = new Date().toISOString() } return jsonData } function toJsonText(groups = [], options = {}) { return JSON.stringify(toJsonData(groups, options), null, 2) } function normalizeProtocolMode(protocolMode) { return protocolMode === PROTOCOL_MODE.MODBUS_RTU ? PROTOCOL_MODE.MODBUS_RTU : PROTOCOL_MODE.STORAGE_ACCESS } function getProtocolStorageKey(protocolMode) { return `${STORAGE_KEY}:${normalizeProtocolMode(protocolMode)}` } function parseJsonGroups(jsonText) { const parsed = typeof jsonText === 'string' ? JSON.parse(jsonText) : jsonText const groups = Array.isArray(parsed) ? parsed : (Array.isArray(parsed && parsed.groups) ? parsed.groups : parsed && parsed.parameterGroups) if (parsed && parsed.type && parsed.type !== JSON_DOCUMENT_TYPE) { throw new Error('JSON 文件不是参数组配置') } if (parsed && parsed.version && parsed.version !== JSON_SCHEMA_VERSION) { throw new Error('JSON 版本不兼容') } if (!Array.isArray(groups)) { throw new Error('JSON 中没有找到寄存器组数组') } return groups } function readStoredGroups(protocolMode = PROTOCOL_MODE.STORAGE_ACCESS) { const wxApi = getWxApi() if (typeof wxApi.getStorageSync !== 'function') return [] try { const storageKey = getProtocolStorageKey(protocolMode) const jsonText = wxApi.getStorageSync(storageKey) if (jsonText) return parseJsonGroups(jsonText).map(cloneImportedGroup) } catch (error) { return [] } return [] } function persistGroups(groups = [], protocolMode = PROTOCOL_MODE.STORAGE_ACCESS) { const wxApi = getWxApi() if (typeof wxApi.setStorageSync !== 'function') return try { wxApi.setStorageSync(getProtocolStorageKey(protocolMode), toJsonText(groups)) } catch (error) {} } function legacyGroupsNeedMigration() { const wxApi = getWxApi() if (typeof wxApi.getStorageSync !== 'function') return false try { return !!wxApi.getStorageSync(STORAGE_KEY) && !wxApi.getStorageSync(STORAGE_MIGRATION_KEY) } catch (error) { return false } } function migrateLegacyGroupsToProtocol(protocolMode = PROTOCOL_MODE.STORAGE_ACCESS) { const wxApi = getWxApi() if (typeof wxApi.getStorageSync !== 'function' || typeof wxApi.setStorageSync !== 'function') return false try { if (!legacyGroupsNeedMigration()) return false const normalizedProtocolMode = normalizeProtocolMode(protocolMode) const legacyJsonText = wxApi.getStorageSync(STORAGE_KEY) const targetStorageKey = getProtocolStorageKey(normalizedProtocolMode) const targetJsonText = wxApi.getStorageSync(targetStorageKey) if (legacyJsonText && !targetJsonText) { persistGroups(parseJsonGroups(legacyJsonText).map(cloneImportedGroup), normalizedProtocolMode) } wxApi.setStorageSync(STORAGE_MIGRATION_KEY, normalizedProtocolMode) return true } catch (error) { return false } } function getShareFileName() { return `parameter-groups-${formatExportStamp()}.json` } async function loadGroupsFromMessageFile() { const file = await loadSelectedFile('message', { encoding: 'utf8', extensionMessage: '请选择 .json 寄存器配置文件', extensions: ['json'], fallbackName: 'parameter-groups.json' }) return parseJsonGroups(file.text).map(cloneImportedGroup) } async function saveGroupsToChat(groups = []) { if (!groups.length) { throw new Error('没有可保存的寄存器组') } const jsonText = toJsonText(groups, { includeExportedAt: true }) await saveTextFileToChat(getShareFileName(), jsonText) return groups.length } module.exports = { JSON_DOCUMENT_TYPE, JSON_SCHEMA_VERSION, STORAGE_KEY, isCancelError, legacyGroupsNeedMigration, loadGroupsFromMessageFile, migrateLegacyGroupsToProtocol, parseJsonGroups, persistGroups, readStoredGroups, saveGroupsToChat, toJsonData, toJsonText }