光伏pc端应用
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

265 lines
8.9 KiB

const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const fs = require('fs')
const { SensorController } = require('./src/business/sensor-controller')
const { UsbSerialBridge } = require('./src/business/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 usbBridge = null
let sensorController = null
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 emit(channel, ...args) {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send(channel, ...args)
}
}
function lastSensorValue(id, opId) {
if (!sensorController) return null
const device = sensorController.devices.find(d => d.id === id) ||
sensorController.devices.find(d => d.sensor === id) ||
sensorController.devices.find(d => d.sensor === String(id || '').split(':')[0])
if (!device) return null
return sensorController.lastValues.get(`${device.sensor}:${opId}`) ?? null
}
function createSensorController() {
sensorController = new SensorController({
onValue: data => emit('sensor-data', data),
onDeviceAttached: device => emit('sensor-attached', { id: device.id, name: device.name }),
onDeviceDetached: device => emit('sensor-detached', { id: device.id, name: device.name }),
onLog: (level, msg) => emit('sensor-log', { level, msg })
})
sensorController.refresh().catch(err => console.error('[sensor] refresh failed:', err.message))
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1280,
height: 800,
fullscreen: false,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: false
}
})
createSensorController()
usbBridge = new UsbSerialBridge(mainWindow, {
onDeviceDiscovered: () => {
if (sensorController) sensorController.refresh().catch(() => {})
}
})
mainWindow.loadFile(path.join(__dirname, 'dist', 'index.html'))
}
function registerAppIpc() {
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', () => emit('app-page-mode', 'offline'))
ipcMain.on('app-online-page', () => emit('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-export-file', (event, filename, content, token) => {
const target = resolveAppFile(filename || `export-${Date.now()}.txt`)
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 || filename, target, true, ''))
.catch(err => event.sender.send('app-file-write-callback', token || filename, target, false, err.message))
})
}
function registerSensorIpc() {
ipcMain.on('app-sensor-open-sync', (event) => {
if (sensorController) sensorController.open().catch(() => {})
event.returnValue = true
})
ipcMain.on('app-sensor-close-sync', (event) => {
event.returnValue = sensorController ? sensorController.close() : true
})
ipcMain.on('app-sensor-is-open-sync', (event) => {
event.returnValue = sensorController ? sensorController.isOpen : false
})
ipcMain.on('sensor-init-all-sync', (event) => {
if (sensorController) sensorController.initAll().catch(() => {})
event.returnValue = true
})
ipcMain.on('sensor-flush-sync', (event) => {
if (sensorController) sensorController.refresh().catch(() => {})
event.returnValue = true
})
ipcMain.on('sensor-list-sync', (event) => {
event.returnValue = sensorController ? sensorController.list() : []
})
ipcMain.on('sensor-init-sync', (event, id) => {
event.returnValue = sensorController ? sensorController.init(id) : false
})
ipcMain.on('sensor-open-sync', (event) => {
if (sensorController) sensorController.open().catch(() => {})
event.returnValue = true
})
ipcMain.on('sensor-close-sync', (event) => {
event.returnValue = sensorController ? sensorController.close() : true
})
ipcMain.on('sensor-operation-sync', (event, id, opId) => {
const cached = lastSensorValue(id, opId)
if (sensorController) {
sensorController.operation(id, opId).catch(() => {})
}
event.returnValue = cached
})
ipcMain.on('sensor-operation-async', (event, id, opId, args) => {
if (!sensorController) {
event.sender.send('sensor-operation-callback', id, opId, null)
return
}
sensorController.operation(id, opId, args)
.then(result => event.sender.send('sensor-operation-callback', id, opId, result))
.catch(err => event.sender.send('sensor-operation-callback', id, opId, err.message))
})
}
function registerLocationAndBluetoothIpc() {
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', () => {})
ipcMain.on('bluetooth-device-select', (event, namePrefix) => {
event.sender.send('bluetooth-device-selected', 'false', namePrefix || '')
})
ipcMain.on('bluetooth-device-open', (event, mac) => {
event.sender.send('bluetooth-device-opened', 'false', mac || '')
event.sender.send('bluetooth-device-state-changed', mac || '', 'UNSUPPORTED')
})
ipcMain.on('bluetooth-device-write', () => {})
ipcMain.on('bluetooth-device-close', () => {})
}
function registerIpc() {
registerAppIpc()
registerSensorIpc()
registerLocationAndBluetoothIpc()
}
app.whenReady().then(() => {
registerIpc()
ensureDir(fileRoot)
createWindow()
app.on('before-quit', () => {
if (sensorController) sensorController.close()
if (usbBridge) usbBridge.disconnectAll()
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})