/* * @Author: 李皓 hao_li_work@163.com * @Date: 2026-07-28 15:22:20 * @LastEditors: 李皓 hao_li_work@163.com * @LastEditTime: 2026-07-28 16:59:56 * @FilePath: \pc\main.js * @Description: * @Version: 1.0.0 */ const { app, BrowserWindow, ipcMain } = require('electron') const path = require('path') const fs = require('fs') const { SerialPort } = require('serialport') const { WitMotionSensor } = require('./sensor') const { UsbSerialBridge } = require('./usb-serial-bridge') const pkg = require('./package.json') const cacheDir = app.getPath('userData') const cacheFile = path.join(cacheDir, 'app-cache.json') const fileRoot = path.join(cacheDir, 'files') let mainWindow = null let sensor = null let usbBridge = null let sensorOpen = false let sensorDevices = [] let lastSensorValues = {} function ensureDir(dir) { try { if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }) } catch {} } function readCache() { try { if (fs.existsSync(cacheFile)) return JSON.parse(fs.readFileSync(cacheFile, 'utf-8')) } catch {} return {} } function writeCache(data) { try { ensureDir(cacheDir) fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2), 'utf-8') } catch {} } function resolveAppFile(inputPath) { const raw = String(inputPath || '').trim() if (!raw) return fileRoot if (path.isAbsolute(raw) && /^[a-zA-Z]:[\\/]/.test(raw)) return raw const relative = raw.replace(/^[/\\]+/, '') return path.join(fileRoot, relative) } function fileList(dir) { try { const target = resolveAppFile(dir) if (!fs.existsSync(target)) return '' const stat = fs.statSync(target) if (stat.isFile()) return target return fs.readdirSync(target).map(name => path.join(target, name)).join('\n') } catch { return '' } } function sensorOperations(id) { return [ { id: 'read-version', name: '获取当前传感器版本' }, { id: 'read-temp', name: '获取设备温度' }, { id: 'read-euler', name: '获取设备角度' }, { id: 'reset-euler', name: '重置角度传感器基准' }, { id: 'read-heading-and-vertical', name: '获取朝向和垂直夹角' } ].map(op => ({ ...op, sensorId: id })) } async function refreshSensorDevices() { try { const ports = await SerialPort.list() sensorDevices = ports .filter(p => p.path && /^COM\d+$/i.test(p.path)) .map(p => ({ id: `HWT6053:${p.path}`, name: 'HWT6053', port: p.path, baudRate: 9600, isOpen: sensorOpen, operations: sensorOperations(`HWT6053:${p.path}`) })) for (const device of sensorDevices) { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('sensor-attached', { id: device.id, name: device.name }) } } } catch (err) { console.error('[sensor] 刷新设备失败:', err.message) sensorDevices = [] } } function normalizeSensorData(data) { if (!data || data.sensor !== 'HWT6053') return data if (data.method === 'temperature') { const value = data.value && typeof data.value === 'object' ? data.value.temp : data.value return { sensor: 'HWT6053', method: 'read-temp', value: String(value ?? '') } } if (data.method === 'angle') { return { sensor: 'HWT6053', method: 'read-euler', value: data.value } } if (data.method === 'magnetic') { return { sensor: 'HWT6053', method: 'read-heading-and-vertical', value: data.value } } return data } function cacheSensorValue(data) { const normalized = normalizeSensorData(data) if (!normalized) return lastSensorValues[normalized.method] = normalized.value if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('sensor-data', normalized) } } function ensureSensor() { if (sensor) return sensor sensor = new WitMotionSensor({ onData: cacheSensorValue, onLog: (level, msg) => { console.log(`[sensor] ${msg}`) if (mainWindow && !mainWindow.isDestroyed()) { try { mainWindow.webContents.send('sensor-log', { level, msg }) } catch {} } } }) return sensor } function openAppSensor() { sensorOpen = true refreshSensorDevices() ensureSensor().connect() return true } function closeAppSensor() { sensorOpen = false if (sensor) sensor.disconnect() sensor = null return true } function runSensorOperation(opId) { if (opId === 'read-version') return pkg.version || '1' if (opId === 'read-temp') return lastSensorValues['read-temp'] ?? null if (opId === 'read-euler') { const value = lastSensorValues['read-euler'] return value == null ? null : JSON.stringify(value) } if (opId === 'read-heading-and-vertical') { const value = lastSensorValues['read-heading-and-vertical'] return value == null ? null : JSON.stringify(value) } if (opId === 'reset-euler') return '0' return null } function createWindow() { mainWindow = new BrowserWindow({ width: 800, height: 600, webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, sandbox: false } }) mainWindow.loadFile('./dist/index.html') usbBridge = new UsbSerialBridge(mainWindow) refreshSensorDevices() } function registerIpc() { ipcMain.on('app-clear-cache-sync', (event) => { writeCache({}) if (mainWindow && !mainWindow.isDestroyed()) mainWindow.reload() event.returnValue = true }) ipcMain.on('app-version-code-sync', (event) => { const major = Number(String(pkg.version || '1').split('.')[0]) || 1 event.returnValue = major }) ipcMain.on('get-cache-sync', (event, key) => { const cache = readCache() event.returnValue = key ? (cache[key] ?? null) : JSON.stringify(cache) }) ipcMain.on('set-cache-sync', (event, key, value) => { const cache = readCache() cache[key] = value writeCache(cache) event.returnValue = true }) ipcMain.handle('get-cache', (_event, key) => { const cache = readCache() return key ? (cache[key] ?? null) : cache }) ipcMain.handle('set-cache', (_event, key, value) => { const cache = readCache() cache[key] = value writeCache(cache) return true }) ipcMain.on('app-offline-page', () => { if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('app-page-mode', 'offline') }) ipcMain.on('app-online-page', () => { if (mainWindow && !mainWindow.isDestroyed()) mainWindow.webContents.send('app-page-mode', 'online') }) ipcMain.on('app-file-list-sync', (event, dir) => { event.returnValue = fileList(dir) }) ipcMain.on('app-file-write-string', (event, filePath, content, token) => { const target = resolveAppFile(filePath) fs.promises.mkdir(path.dirname(target), { recursive: true }) .then(() => fs.promises.writeFile(target, String(content ?? ''), 'utf-8')) .then(() => event.sender.send('app-file-write-callback', token, target, true, '')) .catch(err => event.sender.send('app-file-write-callback', token, target, false, err.message)) }) ipcMain.on('app-file-read-string', (event, filePath, token) => { const target = resolveAppFile(filePath) fs.promises.readFile(target, 'utf-8') .then(content => event.sender.send('app-file-read-callback', token, target, content)) .catch(() => event.sender.send('app-file-read-callback', token, target, null)) }) ipcMain.on('app-file-delete', (event, filePath, token) => { const target = resolveAppFile(filePath) fs.promises.unlink(target) .then(() => event.sender.send('app-file-delete-callback', token, target, true)) .catch(() => event.sender.send('app-file-delete-callback', token, target, false)) }) ipcMain.on('app-sensor-open-sync', (event) => { event.returnValue = openAppSensor() }) ipcMain.on('app-sensor-close-sync', (event) => { event.returnValue = closeAppSensor() }) ipcMain.on('app-sensor-is-open-sync', (event) => { event.returnValue = sensorOpen }) ipcMain.on('sensor-init-all-sync', (event) => { refreshSensorDevices() event.returnValue = true }) ipcMain.on('sensor-flush-sync', (event) => { refreshSensorDevices() event.returnValue = true }) ipcMain.on('sensor-list-sync', (event) => { event.returnValue = sensorDevices }) ipcMain.on('sensor-init-sync', (event, id) => { event.returnValue = sensorDevices.some(device => device.id === id) }) ipcMain.on('sensor-open-sync', (event) => { event.returnValue = openAppSensor() }) ipcMain.on('sensor-close-sync', (event) => { event.returnValue = closeAppSensor() }) ipcMain.on('sensor-operation-sync', (event, _id, opId) => { event.returnValue = runSensorOperation(opId) }) ipcMain.on('sensor-operation-async', (event, id, opId) => { const result = runSensorOperation(opId) event.sender.send('sensor-operation-callback', id, opId, result) }) ipcMain.on('app-location-is-support-sync', (event) => { event.returnValue = false }) ipcMain.on('app-location-is-open-sync', (event) => { event.returnValue = false }) ipcMain.on('app-location-open', (event) => { event.sender.send('app-location-opened', 1) }) ipcMain.on('app-location-close', () => {}) } app.whenReady().then(() => { registerIpc() ensureDir(fileRoot) createWindow() app.on('before-quit', () => { if (sensor) sensor.disconnect() if (usbBridge) usbBridge.disconnectAll() }) })