Skip to content

Commit 662cca2

Browse files
committed
added serial debug interface
1 parent b608a8b commit 662cca2

File tree

13 files changed

+241
-36
lines changed

13 files changed

+241
-36
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ then just run it with dev to start
2323
# Tasks
2424
## urgent
2525
- [x] check if a keyboard is connected (usb drive) in the keyboard selector preview
26-
- [ ] show serial output in the gui
27-
- [ ] guides etc for setup + split workflow
26+
- [x] show serial output in the gui
27+
- [ ] automatically get the correct serial device (by serial number)
28+
- [ ] guides etc for setup + split workflow | help menu + videos
2829
- [ ] share pog.json files
2930
- [ ] check if the controller you use even has the pin you specified (controller lookup and serial to get pins )
3031
- [ ] bluetooth workflow

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "pog",
3-
"version": "0.9.5",
3+
"version": "1.0.0",
44
"description": "A KMK firmware configurator",
55
"main": "./out/main/index.js",
66
"author": "Jan Lunge",

src/main/index.ts

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { app, shell, BrowserWindow, ipcMain, Menu } from 'electron'
22
import { join } from 'path'
33
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
44
import icon from '../../resources/icon.png?asset'
5-
import {checkForUSBKeyboards, handleSelectDrive, selectKeyboard} from './selectKeyboard'
5+
import { checkForUSBKeyboards, handleSelectDrive, selectKeyboard } from './selectKeyboard'
66
import { updateFirmware } from './kmkUpdater'
77
import { handleKeymapSave, saveConfiguration } from './saveConfig'
88
import { autoUpdater } from 'electron-updater'
@@ -125,7 +125,8 @@ const createWindow = async () => {
125125
...(process.platform === 'linux' ? { icon } : {}),
126126
webPreferences: {
127127
preload: join(__dirname, '../preload/index.js'),
128-
sandbox: false
128+
sandbox: false,
129+
experimentalFeatures: true
129130
}
130131
})
131132

@@ -178,6 +179,22 @@ app.on('window-all-closed', () => {
178179
app.quit()
179180
}
180181
})
182+
app.on('before-quit', (event) => {
183+
// Prevent the default behavior of this event
184+
if (debugPort !== undefined) {
185+
event.preventDefault()
186+
187+
console.log('Preparing to quit...')
188+
debugPort.close(() => {
189+
console.log('Port closed')
190+
debugPort = undefined
191+
app.quit()
192+
})
193+
} else {
194+
// Now allow the app to continue quitting
195+
app.quit()
196+
}
197+
})
181198

182199
// In this file you can include the rest of your app"s specific main process
183200
// code. You can also put them in separate files and require them here.
@@ -193,6 +210,10 @@ ipcMain.handle('updateFirmware', () => updateFirmware())
193210
ipcMain.on('saveConfiguration', (_event, data) => saveConfiguration(data))
194211
ipcMain.handle('checkForUSBKeyboards', (_event, data) => checkForUSBKeyboards(data))
195212
ipcMain.handle('selectKeyboard', (_event, data) => selectKeyboard(data))
213+
ipcMain.handle('serialPorts', (_event, data) => checkSerialDevices())
214+
ipcMain.on('serialSend', (_event, data) => sendSerial(data))
215+
ipcMain.handle('serialConnect', (_event, data) => serialConnect(data))
216+
ipcMain.handle('openExternal', (_event, data) => openExternal(data))
196217

197218
autoUpdater.on('update-available', () => {
198219
if (mainWindow) mainWindow.webContents.send('update_available')
@@ -246,7 +267,7 @@ const scanForKeyboards = async () => {
246267
circuitPythonPorts.map(async (a) => await timeout(getBoardInfo(a), 2000))
247268
)) as {
248269
status: 'fulfilled' | 'rejected'
249-
value: { name: string; id: string; path: string }
270+
value: { name: string; id: string; path: string }
250271
}[]
251272
const filteredBoards: { name: string; id: string; path: string }[] = boards
252273
.filter((a) => a.value !== undefined)
@@ -435,3 +456,73 @@ const deselectKeyboard = () => {
435456
connectedKeyboardPort.close()
436457
}
437458
}
459+
460+
const sendSerial = (message) => {
461+
console.log('sending serial', message)
462+
mainWindow?.webContents.send('serialData', { message: `> sent: ${message}\n` })
463+
if (message === 'ctrlc') {
464+
message = Buffer.from('\x03\x03', 'utf8')
465+
} else if (message === 'ctrld') {
466+
message = Buffer.from('\x04', 'utf8')
467+
} else {
468+
const buffermessage = Buffer.from(message + `\r\n`, 'utf8')
469+
message = buffermessage
470+
}
471+
debugPort.write(message, (err) => {
472+
if (err) {
473+
console.error('error sending serial', err)
474+
}
475+
})
476+
}
477+
478+
const checkSerialDevices = async () => {
479+
try {
480+
console.log('checking serial devices')
481+
const ports = await serialPort.SerialPort.list()
482+
483+
if (ports.length === 0) {
484+
console.log('No serial ports found')
485+
return []
486+
}
487+
488+
const returnPorts = ports.map((port) => {
489+
return {
490+
port: port.path,
491+
manufacturer: port.manufacturer,
492+
serialNumber: port.serialNumber,
493+
// Add more attributes here if needed
494+
}
495+
})
496+
console.log('found serial ports', returnPorts)
497+
return returnPorts
498+
} catch (error) {
499+
console.error('Error fetching the list of serial ports:', error)
500+
return []
501+
}
502+
}
503+
504+
let debugPort: any = undefined
505+
const serialConnect = async (port) => {
506+
if (debugPort !== undefined) {
507+
debugPort.close(() => {
508+
console.log('closed serial port')
509+
debugPort = undefined
510+
})
511+
}
512+
console.log('connecting to serial port', port)
513+
try {
514+
debugPort = new serialPort.SerialPort({ path: port, baudRate, autoOpen: true }, (e) => {})
515+
const parser = debugPort.pipe(new ReadlineParser({ delimiter: '\n' }))
516+
parser.on('data', (data) => {
517+
console.log('got data from serial', data)
518+
mainWindow?.webContents.send('serialData', { message: data })
519+
})
520+
console.log('connected to serial port and listening for messages', port)
521+
} catch (e) {
522+
console.log('error connecting to serial port', e)
523+
}
524+
}
525+
526+
const openExternal = (url) => {
527+
shell.openExternal(url)
528+
}

src/main/selectKeyboard.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,19 @@ const loadKeyboard = async (path) => {
4545
}
4646
export const selectKeyboard = async ({ path, id }: { path: string; id: string }) => {
4747
console.log(path,id)
48+
if(id){
49+
// connect serial if available
50+
const port = serialBoards.value.find((a) => a.id === id)
51+
if(!port) return { error:'not a serial keyboard' }
52+
console.log(serialBoards, id)
53+
await connectSerialKeyboard(port)
54+
connectedKeyboardPort.write('info\n')
55+
}
4856
if (path) {
4957
console.log('checking keyboard files for', path)
5058
return await loadKeyboard(path)
5159
} else if (id) {
5260
console.log('connecting serial keyboard')
53-
const port = serialBoards.value.find((a) => a.id === id)
54-
console.log(serialBoards, id)
55-
await connectSerialKeyboard(port)
56-
connectedKeyboardPort.write('info\n')
5761
return { success: true }
5862
}
5963
return { error: 'not all args provided' }

src/preload/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ export const API = {
1717
},
1818
rescanKeyboards: () => ipcRenderer.invoke('rescanKeyboards'),
1919
checkForUSBKeyboards: (data) => ipcRenderer.invoke('checkForUSBKeyboards', data),
20-
deselectKeyboard: () => ipcRenderer.invoke('deselectKeyboard')
21-
20+
deselectKeyboard: () => ipcRenderer.invoke('deselectKeyboard'),
21+
serialData: (callback) =>
22+
ipcRenderer.on('serialData', callback),
23+
serialPorts: () =>
24+
ipcRenderer.invoke('serialPorts'),
25+
serialSend: (data) => ipcRenderer.send('serialSend', data),
26+
serialConnect: (data) => ipcRenderer.invoke('serialConnect', data),
27+
openExternal: (data) => ipcRenderer.invoke('openExternal', data),
2228
}
2329

2430
// Use `contextBridge` APIs to expose Electron APIs to

src/renderer/src/App.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { computed } from 'vue'
3-
import {addToHistory, keyboardHistory, keyboardStore, notifications, serialKeyboards} from './store'
3+
import {addToHistory, keyboardStore, notifications, serialKeyboards} from './store'
44
import { useRouter} from "vue-router";
55
const router = useRouter()
66
const store = computed(() => {

src/renderer/src/components/KmkInstaller.vue

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,16 @@
2626
</div>
2727
</div>
2828
<div class="mt-8 flex justify-center">
29-
<button class="btn-primary btn mt-4 block" @click="$emit('next')">
30-
{{ keyboardStore.firmwareInstalled ? 'Next' : 'skip' }}
29+
<button v-if="keyboardStore.firmwareInstalled" class="btn btn-primary mt-4 block" @click="$emit('next')">
30+
Next
31+
</button>
32+
<button v-else class="btn mt-4 block" @click="$emit('next')">
33+
I install it manually
34+
3135
</button>
3236
</div>
3337
<div v-if="[''].includes(kmkInstallState)" class="mt-8 flex justify-center items-center flex-col">
34-
<button class="btn-primary btn mt-8" @click="updateKMK">install KMK</button>
38+
<button class="btn-primary btn mt-8" @click="updateKMK"> {{keyboardStore.firmwareInstalled? 'update': 'install'}} KMK</button>
3539

3640
</div>
3741
<div class="mt-4 flex flex-col items-center justify-center" v-if="['downloading', 'copying', 'unpacking'].includes(kmkInstallState)" >
Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,85 @@
11
<template>
2-
$END$
2+
here you can connect to a keyboard via serial for more verbose debug output <br />
3+
not this is still very much a work in progress <br />
4+
first create a reload key on your keymap and press it to reload the keyboard <br />
5+
then press connect to connect to the keyboard (the keyboard has 2 serial consoles connect to the
6+
first one with the lower number for debugging) <br />
7+
then you can see the output below <br />
8+
if the output does not appear unplug and replug the keyboard and try again (this is the WIP
9+
part)<br />
10+
11+
<div>
12+
<div class="mb-2 flex justify-between gap-2">
13+
<select v-model="selectedPort" class="select">
14+
<option v-for="port in sortedPorts" :key="port.port" :value="port.port">
15+
{{ port.manufacturer }} - {{ port.port }} - {{ port.serialNumber }}
16+
</option>
17+
</select>
18+
<button class="btn" @click="connect">connect</button>
19+
</div>
20+
<textarea
21+
v-model="output"
22+
class="textarea w-full p-2"
23+
style="min-height: 200px"
24+
readonly
25+
></textarea>
26+
<div class="flex gap-2">
27+
<input v-model="inputData" class="input input-sm w-full" @keyup.enter="sendData" />
28+
<button class="btn-sm btn" @click="sendData">Send</button>
29+
</div>
30+
<div class="flex">
31+
<button class="btn-sm btn" @click="enterRepl">enter REPL</button>
32+
<button class="btn-sm btn" @click="exitRepl">exit REPL</button>
33+
</div>
34+
</div>
335
</template>
436

537
<script lang="ts" setup>
38+
import {computed, onMounted, ref} from 'vue'
39+
import {keyboardStore} from "../store";
640
7-
</script>
41+
const output = ref('')
42+
const inputData = ref('')
43+
const ports = ref<any[]>([])
44+
const selectedPort = ref('')
45+
const sortedPorts = computed(() => {
46+
return [ ...ports.value ].sort((a, b) => {
47+
if (a.port < b.port) {
48+
return -1
49+
}
50+
if (a.port > b.port) {
51+
return 1
52+
}
53+
return 0
54+
}).filter(a=>a.serialNumber !== undefined)
55+
})
56+
onMounted(async () => {
57+
ports.value = await window.api.serialPorts()
58+
// select default port for the one with the current serial number
859
9-
<style lang="scss" scoped>
60+
window.api.serialData((event, data) => {
61+
console.log(event, data)
62+
output.value += data.message
63+
})
64+
console.log(keyboardStore)
65+
})
66+
67+
const sendData = () => {
68+
window.api.serialSend(inputData.value)
69+
inputData.value = ''
70+
}
71+
const connect = () => {
72+
window.api.serialConnect(selectedPort.value)
73+
}
74+
const enterRepl = () => {
75+
window.api.serialSend('ctrlc')
76+
setTimeout(() => {
77+
window.api.serialSend('ctrlc')
78+
}, 1000)
79+
}
80+
const exitRepl = () => {
81+
window.api.serialSend('ctrld')
82+
}
83+
</script>
1084

11-
</style>
85+
<style lang="scss" scoped></style>

src/renderer/src/helpers/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,10 @@ export const renderLabel = (keycode: string) => {
306306
MRWD: { label: 'Prev Track', icon: 'mdi-skip-previous' },
307307
MFFD: { label: 'Next Track', icon: 'mdi-skip-next' },
308308
send_string: { label: 'String' },
309-
RESET: { label: 'Reset' }
309+
RESET: { label: 'Reset' },
310+
RELOAD: { label: 'Reload' },
311+
DEBUG: { label: 'Debug' }
312+
310313
}
311314

312315
const keylabel: {

src/renderer/src/router/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import RawKeymapEditor from '../components/RawKeymapEditor.vue'
1313
import KeyboardName from '../components/KeyboardName.vue'
1414
import CoordMap from '../components/CoordMap.vue'
1515
import Community from "../components/Community.vue";
16+
import Debug from "../components/debug.vue";
1617

1718
const router = createRouter({
1819
history: createWebHashHistory(),
@@ -90,6 +91,11 @@ const router = createRouter({
9091
path: 'community',
9192
component: Community,
9293
name: 'Community'
94+
},
95+
{
96+
path: 'debug',
97+
component: Debug,
98+
name: 'Debug'
9399
}
94100
]
95101
}

0 commit comments

Comments
 (0)