Skip to content

Commit ce71de9

Browse files
committed
Use serial bridge to execute serial commands
1 parent 8fe633c commit ce71de9

File tree

5 files changed

+280
-170
lines changed

5 files changed

+280
-170
lines changed

backend/bridge/serial-bridge.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
const { ipcRenderer } = require('electron')
2+
const path = require('path')
3+
4+
const SerialBridge = {
5+
loadPorts: async () => {
6+
return await ipcRenderer.invoke('serial', 'loadPorts')
7+
},
8+
connect: async (path) => {
9+
return await ipcRenderer.invoke('serial', 'connect', path)
10+
},
11+
disconnect: async () => {
12+
return await ipcRenderer.invoke('serial', 'disconnect')
13+
},
14+
run: async (code) => {
15+
return await ipcRenderer.invoke('serial', 'run', code)
16+
},
17+
execFile: async (path) => {
18+
return await ipcRenderer.invoke('serial', 'execFile', path)
19+
},
20+
getPrompt: async () => {
21+
return await ipcRenderer.invoke('serial', 'getPrompt')
22+
},
23+
keyboardInterrupt: async () => {
24+
await ipcRenderer.invoke('serial', 'keyboardInterrupt')
25+
return Promise.resolve()
26+
},
27+
reset: async () => {
28+
await ipcRenderer.invoke('serial', 'reset')
29+
return Promise.resolve()
30+
},
31+
eval: (d) => {
32+
return ipcRenderer.invoke('serial', 'eval', d)
33+
},
34+
onData: (callback) => {
35+
ipcRenderer.on('serial-on-data', (event, data) => {
36+
callback(data)
37+
})
38+
},
39+
listFiles: async (folder) => {
40+
return await ipcRenderer.invoke('serial', 'listFiles', folder)
41+
},
42+
ilistFiles: async (folder) => {
43+
return await ipcRenderer.invoke('serial', 'ilistFiles', folder)
44+
},
45+
loadFile: async (file) => {
46+
return await ipcRenderer.invoke('serial', 'loadFile', file)
47+
},
48+
removeFile: async (file) => {
49+
return await ipcRenderer.invoke('serial', 'removeFile', file)
50+
},
51+
saveFileContent: async (filename, content, dataConsumer) => {
52+
return await ipcRenderer.invoke('serial', 'saveFileContent', filename, content, dataConsumer)
53+
},
54+
uploadFile: async (src, dest, dataConsumer) => {
55+
return await ipcRenderer.invoke('serial', 'uploadFile', src, dest, dataConsumer)
56+
},
57+
downloadFile: async (src, dest) => {
58+
let contents = await ipcRenderer.invoke('serial', 'loadFile', src)
59+
return ipcRenderer.invoke('save-file', dest, contents)
60+
},
61+
renameFile: async (oldName, newName) => {
62+
return await ipcRenderer.invoke('serial', 'renameFile', oldName, newName)
63+
},
64+
onConnectionClosed: async (callback) => {
65+
ipcRenderer.on('serial-on-connection-closed', (event) => {
66+
callback()
67+
})
68+
},
69+
createFolder: async (folder) => {
70+
return await ipcRenderer.invoke('serial', 'createFolder', folder)
71+
},
72+
removeFolder: async (folder) => {
73+
return await ipcRenderer.invoke('serial', 'removeFolder', folder)
74+
},
75+
getNavigationPath: (navigation, target) => {
76+
return path.posix.join(navigation, target)
77+
},
78+
getFullPath: (root, navigation, file) => {
79+
return path.posix.join(root, navigation, file)
80+
},
81+
getParentPath: (navigation) => {
82+
return path.posix.dirname(navigation)
83+
},
84+
fileExists: async (filePath) => {
85+
return await ipcRenderer.invoke('serial', 'fileExists', filePath)
86+
}
87+
}
88+
89+
module.exports = SerialBridge

backend/ipc.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const fs = require('fs')
2+
const Serial = require('./serial.js')
3+
let serial
4+
25
const {
36
openFolderDialog,
47
listFolder,
@@ -7,6 +10,8 @@ const {
710
} = require('./helpers.js')
811

912
module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
13+
serial = new Serial(win)
14+
1015
ipcMain.handle('open-folder', async (event) => {
1116
console.log('ipcMain', 'open-folder')
1217
const folder = await openFolderDialog(win)
@@ -144,4 +149,9 @@ module.exports = function registerIPCHandlers(win, ipcMain, app, dialog) {
144149
ipcMain.handle('prepare-reload', async (event) => {
145150
return win.webContents.send('before-reload')
146151
})
152+
153+
ipcMain.handle('serial', (event, command, ...args) => {
154+
console.debug('Handling IPC serial command:', command, ...args)
155+
return serial[command](...args)
156+
})
147157
}

backend/serial.js

Lines changed: 101 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,113 @@
11
const MicroPython = require('micropython.js')
22

3-
const board = new MicroPython()
4-
board.chunk_size = 192
5-
board.chunk_sleep = 200
6-
7-
const Serial = {
8-
loadPorts: async () => {
9-
let ports = await board.list_ports()
10-
return ports.filter(p => p.vendorId && p.productId)
11-
},
12-
connect: async (path) => {
13-
return board.open(path)
14-
},
15-
disconnect: async () => {
16-
return board.close()
17-
},
18-
run: async (code) => {
19-
return board.run(code)
20-
},
21-
execFile: async (path) => {
22-
return board.execfile(path)
23-
},
24-
getPrompt: async () => {
25-
return board.get_prompt()
26-
},
27-
keyboardInterrupt: async () => {
28-
await board.stop()
29-
return Promise.resolve()
30-
},
31-
reset: async () => {
32-
await board.stop()
33-
await board.exit_raw_repl()
34-
await board.reset()
35-
return Promise.resolve()
36-
},
37-
eval: (d) => {
38-
return board.eval(d)
39-
},
40-
onData: (fn) => {
41-
board.serial.on('data', fn)
42-
},
43-
listFiles: async (folder) => {
44-
return board.fs_ls(folder)
45-
},
46-
ilistFiles: async (folder) => {
47-
return board.fs_ils(folder)
48-
},
49-
loadFile: async (file) => {
50-
const output = await board.fs_cat_binary(file)
51-
return output || ''
52-
},
53-
removeFile: async (file) => {
54-
return board.fs_rm(file)
55-
},
56-
saveFileContent: async (filename, content, dataConsumer) => {
57-
return board.fs_save(content || ' ', filename, dataConsumer)
58-
},
59-
uploadFile: async (src, dest, dataConsumer) => {
60-
return board.fs_put(src, dest.replaceAll(path.win32.sep, path.posix.sep), dataConsumer)
61-
},
62-
downloadFile: async (src, dest) => {
63-
let contents = await Serial.loadFile(src)
64-
return ipcRenderer.invoke('save-file', dest, contents)
65-
},
66-
renameFile: async (oldName, newName) => {
67-
return board.fs_rename(oldName, newName)
68-
},
69-
onConnectionClosed: async (fn) => {
70-
board.serial.on('close', fn)
71-
},
72-
createFolder: async (folder) => {
73-
return await board.fs_mkdir(folder)
74-
},
75-
removeFolder: async (folder) => {
76-
return await board.fs_rmdir(folder)
77-
},
78-
getNavigationPath: (navigation, target) => {
79-
return path.posix.join(navigation, target)
80-
},
81-
getFullPath: (root, navigation, file) => {
82-
return path.posix.join(root, navigation, file)
83-
},
84-
getParentPath: (navigation) => {
85-
return path.posix.dirname(navigation)
86-
},
87-
fileExists: async (filePath) => {
88-
// !!!: Fix this on micropython.js level
89-
// ???: Check if file exists is not part of mpremote specs
90-
const output = await board.run(`
3+
class Serial {
4+
constructor(win) {
5+
this.win = win
6+
this.board = new MicroPython()
7+
this.board.chunk_size = 192
8+
this.board.chunk_sleep = 200
9+
}
10+
11+
async loadPorts() {
12+
let ports = await this.board.list_ports()
13+
return ports.filter(p => p.vendorId && p.productId)
14+
}
15+
16+
async connect(path) {
17+
await this.board.open(path)
18+
this.registerCallbacks()
19+
}
20+
21+
async disconnect() {
22+
return await this.board.close()
23+
}
24+
25+
async run(code) {
26+
return await this.board.run(code)
27+
}
28+
29+
async execFile(path) {
30+
return await this.board.execfile(path)
31+
}
32+
33+
async getPrompt() {
34+
return await this.board.get_prompt()
35+
}
36+
37+
async keyboardInterrupt() {
38+
await this.board.stop()
39+
return Promise.resolve()
40+
}
41+
42+
async reset() {
43+
await this.board.stop()
44+
await this.board.exit_raw_repl()
45+
await this.board.reset()
46+
return Promise.resolve()
47+
}
48+
49+
async eval(d) {
50+
return await this.board.eval(d)
51+
}
52+
53+
registerCallbacks() {
54+
this.board.serial.on('data', (data) => {
55+
this.win.webContents.send('serial-on-data', data)
56+
})
57+
58+
this.board.serial.on('close', () => {
59+
this.win.webContents.send('serial-on-connection-closed')
60+
})
61+
}
62+
63+
async listFiles(folder) {
64+
return await this.board.fs_ls(folder)
65+
}
66+
67+
async ilistFiles(folder) {
68+
return await this.board.fs_ils(folder)
69+
}
70+
71+
async loadFile(file) {
72+
const output = await this.board.fs_cat_binary(file)
73+
return output || ''
74+
}
75+
76+
async removeFile(file) {
77+
return await this.board.fs_rm(file)
78+
}
79+
80+
async saveFileContent(filename, content, dataConsumer) {
81+
return await this.board.fs_save(content || ' ', filename, dataConsumer)
82+
}
83+
84+
async uploadFile(src, dest, dataConsumer) {
85+
return await this.board.fs_put(src, dest.replaceAll(path.win32.sep, path.posix.sep), dataConsumer)
86+
}
87+
88+
async renameFile(oldName, newName) {
89+
return await this.board.fs_rename(oldName, newName)
90+
}
91+
92+
async createFolder(folder) {
93+
return await this.board.fs_mkdir(folder)
94+
}
95+
96+
async removeFolder(folder) {
97+
return await this.board.fs_rmdir(folder)
98+
}
99+
100+
async fileExists(filePath) {
101+
const output = await this.board.run(`
91102
import os
92103
try:
93104
os.stat("${filePath}")
94105
print(0)
95106
except OSError:
96107
print(1)
97108
`)
98-
return output[2] === '0'
99-
}
109+
return output[2] === '0'
110+
}
100111
}
101112

102113
module.exports = Serial

preload.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
console.log('preload')
22
const { contextBridge, ipcRenderer } = require('electron')
33
const path = require('path')
4-
const Serial = require('./backend/serial.js')
4+
const SerialBridge = require('./backend/bridge/serial-bridge.js')
55

66
const { platform } = require('process')
77

@@ -101,6 +101,6 @@ const Window = {
101101

102102
}
103103

104-
contextBridge.exposeInMainWorld('BridgeSerial', Serial)
104+
contextBridge.exposeInMainWorld('BridgeSerial', SerialBridge)
105105
contextBridge.exposeInMainWorld('BridgeDisk', Disk)
106106
contextBridge.exposeInMainWorld('BridgeWindow', Window)

0 commit comments

Comments
 (0)