working on serial connection to ui
This commit is contained in:
14
src/App.vue
14
src/App.vue
@@ -4,6 +4,7 @@ import DevicePreview from '@/components/device/DevicePreview.vue'
|
||||
import ConfigPane from '@/components/config/ConfigPane.vue'
|
||||
import Navbar from '@/components/navbar/Navbar.vue'
|
||||
import { useStore } from '@/store'
|
||||
import { useMessageHandlers } from '@/device'
|
||||
|
||||
const { electron } = window
|
||||
const store = useStore()
|
||||
@@ -21,7 +22,18 @@ electron?.onMenu((key) => {
|
||||
}
|
||||
})
|
||||
|
||||
store.fetchProfiles()
|
||||
store.fetchProfiles() // TODO remove me!
|
||||
|
||||
// handle device events
|
||||
const handlers = useMessageHandlers(store)
|
||||
window.nanodevices.on_event('device-attached', (evt, deviceid, data) => store.device_attached(deviceid))
|
||||
window.nanodevices.on_event('device-detached', (evt, deviceid, data) => store.device_detached(deviceid))
|
||||
window.nanodevices.on_event('device-error', (evt, deviceid, data) => { /* TODO handle connection errors */ })
|
||||
window.nanodevices.on_event('connected', (evt, deviceid, data) => store.device_connected(deviceid))
|
||||
window.nanodevices.on_event('disconnected', (evt, deviceid, data) => store.device_disconnected(deviceid))
|
||||
window.nanodevices.on_event('update', (evt, deviceid, data) => { handlers.handle_message(data) })
|
||||
// get list of the currently attached devices
|
||||
window.nanodevices.list_devices().then((devs)=>store.init_devices(devs))
|
||||
|
||||
</script>
|
||||
<template>
|
||||
|
||||
@@ -8,8 +8,11 @@ const NANO_BAUD_RATE = 115200;
|
||||
|
||||
|
||||
class NanoDevices extends EventEmitter {
|
||||
all_nano_devices = {};
|
||||
connected_nano_devices = {};
|
||||
constructor() {
|
||||
super();
|
||||
this.all_nano_devices = {};
|
||||
this.connected_nano_devices = {};
|
||||
}
|
||||
|
||||
_list() {
|
||||
let p = new Promise((resolve, reject) => {
|
||||
@@ -49,7 +52,12 @@ class NanoDevices extends EventEmitter {
|
||||
let lines = connected_port.data.split('\n');
|
||||
if (lines.length > 1) {
|
||||
for (let i = 0; i < lines.length - 1; i++) {
|
||||
this.emit('nanodevices:update', connected_port.serialNumber, lines[i]);
|
||||
if (lines[i].length > 0) {
|
||||
if (lines[i].startsWith('{')) // if its a json object
|
||||
this.emit('nanodevices:update', connected_port.serialNumber, lines[i]);
|
||||
else
|
||||
console.log("Device: "+lines[i]); // otherwise just log it
|
||||
}
|
||||
}
|
||||
connected_port.data = lines[lines.length - 1];
|
||||
}
|
||||
@@ -80,58 +88,63 @@ class NanoDevices extends EventEmitter {
|
||||
|
||||
|
||||
async connect(deviceid) {
|
||||
let p = new Promise();
|
||||
let nano_device = this.all_nano_devices[deviceid];
|
||||
if (nano_device === undefined) {
|
||||
p.reject('Device not attached');
|
||||
}
|
||||
else {
|
||||
let port = new SerialPort(nano_device.path, { baudRate: NANO_BAUD_RATE, autoOpen: false });
|
||||
port.on('error', (err) => {
|
||||
// forward error to FE
|
||||
this.emit('nanodevices:error', nano_device.serialNumber, err);
|
||||
});
|
||||
port.on('close', (err) => {
|
||||
if (err && err.disconnected) {
|
||||
// forward close to FE
|
||||
this.emit('nanodevices:disconnected', nano_device.serialNumber);
|
||||
}
|
||||
delete this.connected_nano_devices[nano_device.serialNumber];
|
||||
});
|
||||
port.on('open', () => {
|
||||
p.resolve(nano_device.serialNumber);
|
||||
this.connected_nano_devices[nano_device.serialNumber] = { port: port, data: '' };
|
||||
this.emit('nanodevices:connected', nano_device.serialNumber);
|
||||
});
|
||||
port.on('data', (data) => {
|
||||
let connected_port = this.connected_nano_devices[nano_device.serialNumber];
|
||||
this._handle_data(connected_port, data);
|
||||
});
|
||||
port.open((err)=>{
|
||||
if (err) {
|
||||
p.reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
const nanodevices = this;
|
||||
let p = new Promise((resolve, reject) => {
|
||||
let nano_device = nanodevices.all_nano_devices[deviceid];
|
||||
if (nano_device === undefined) {
|
||||
reject('Device not attached');
|
||||
}
|
||||
else {
|
||||
console.log('nano_device', nano_device);
|
||||
let port = new SerialPort({ path: nano_device.path, baudRate: NANO_BAUD_RATE, autoOpen: false });
|
||||
port.on('error', (err) => {
|
||||
// forward error to FE
|
||||
nanodevices.emit('nanodevices:error', nano_device.serialNumber, err);
|
||||
});
|
||||
port.on('close', (err) => {
|
||||
if (err && err.disconnected) {
|
||||
// forward close to FE
|
||||
nanodevices.emit('nanodevices:disconnected', nano_device.serialNumber);
|
||||
}
|
||||
delete nanodevices.connected_nano_devices[nano_device.serialNumber];
|
||||
});
|
||||
port.on('open', () => {
|
||||
resolve(nano_device.serialNumber);
|
||||
nanodevices.connected_nano_devices[nano_device.serialNumber] = { port: port, data: '' };
|
||||
nanodevices.emit('nanodevices:connected', nano_device.serialNumber);
|
||||
});
|
||||
port.on('data', (data) => {
|
||||
let connected_port = nanodevices.connected_nano_devices[nano_device.serialNumber];
|
||||
nanodevices._handle_data(connected_port, data);
|
||||
});
|
||||
port.open((err)=>{
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return p;
|
||||
};
|
||||
|
||||
|
||||
disconnect(deviceid) {
|
||||
let p = new Promise();
|
||||
let nano_device = this.all_nano_devices[deviceid];
|
||||
if (nano_device === undefined) {
|
||||
p.reject('Device not attached');
|
||||
}
|
||||
else {
|
||||
if (this.connected_nano_devices[nano_device.serialNumber] === undefined) {
|
||||
p.reject('Device not connected');
|
||||
const nanodevices = this;
|
||||
let p = new Promise((resolve, reject) => {
|
||||
let nano_device = nanodevices.all_nano_devices[deviceid];
|
||||
if (nano_device === undefined) {
|
||||
reject('Device not attached');
|
||||
}
|
||||
else {
|
||||
nano_device.close();
|
||||
p.resolve(nano_device.serialNumber);
|
||||
if (nanodevices.connected_nano_devices[nano_device.serialNumber] === undefined) {
|
||||
reject('Device not connected');
|
||||
}
|
||||
else {
|
||||
nano_device.close();
|
||||
resolve(nano_device.serialNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return p;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<div class="flex gap-2">
|
||||
<MenubarMenu>
|
||||
<MenubarTrigger class="app-titlebar-button">
|
||||
<template v-if="store.multipleDevicesConnected">
|
||||
Devices<span class="text-zinc-500"> (2)</span>
|
||||
<template v-if="store.numAttachedDevices!==1">
|
||||
Devices<span class="text-zinc-500"> ({{ ""+store.numAttachedDevices }})</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
Device
|
||||
|
||||
64
src/device.js
Normal file
64
src/device.js
Normal file
@@ -0,0 +1,64 @@
|
||||
|
||||
|
||||
|
||||
export const useMessageHandlers = function(store) {
|
||||
|
||||
return {
|
||||
|
||||
handle_message: (jsonstr) => {
|
||||
let message = JSON.parse(jsonstr);
|
||||
if (message.hasOwnProperty('event')) {
|
||||
this.handle_event_message(message);
|
||||
}
|
||||
if (message.hasOwnProperty('p')) {
|
||||
this.handle_profile_message(message);
|
||||
}
|
||||
if (message.hasOwnProperty('profiles')) {
|
||||
this.handle_profiles_message(message);
|
||||
}
|
||||
if (message.hasOwnProperty('settings')) {
|
||||
this.handle_settings_message(message);
|
||||
}
|
||||
if (message.hasOwnProperty('error')) {
|
||||
this.handle_error_message(message);
|
||||
}
|
||||
if (message.hasOwnProperty('debug')) {
|
||||
console.log("Device: DEBUG: ", message.debug);
|
||||
}
|
||||
if (message.hasOwnProperty('idle')) { // TODO remove
|
||||
console.log("Device present, idle since: ", message.idle);
|
||||
}
|
||||
},
|
||||
|
||||
handle_event_message: (event) => {
|
||||
if (event.ks){
|
||||
store.update_keystates(event.ks);
|
||||
}
|
||||
if (event.hasOwnProperty('a')) {
|
||||
store.update_knob_position(event.t, event.a, event.v);
|
||||
}
|
||||
},
|
||||
|
||||
handle_profile_message: (profile) => {
|
||||
// TODO update profile
|
||||
},
|
||||
|
||||
handle_profiles_message: (profiles) => {
|
||||
// TODO update profiles
|
||||
},
|
||||
|
||||
handle_settings_message: (settings) => {
|
||||
if (settings.hasOwnProperty('deviceName')) {
|
||||
store.update_device_name(settings.deviceName);
|
||||
}
|
||||
// TODO update other settings - maybe this should be in a separate store? Or in the main store, but move these ifs to a action method in the store?
|
||||
},
|
||||
|
||||
handle_error_message: (error) => {
|
||||
console.error("Device: ERROR: ", error.error);
|
||||
// TODO show/handle error in UI?
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
||||
@@ -129,7 +129,7 @@ const createLoadingWindow = (mainWindow) => {
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.whenReady().then(() => {
|
||||
ipcMain.handle('nanodevices:list_devices', ()=>nanodevices.list_devices())
|
||||
ipcMain.handle('nanodevices:connect', nanodevices.connect)
|
||||
ipcMain.handle('nanodevices:connect', (event, deviceid)=>nanodevices.connect(deviceid))
|
||||
ipcMain.handle('nanodevices:disconnect', nanodevices.disconnect)
|
||||
ipcMain.handle('nanodevices:send', nanodevices.send)
|
||||
const mainWindow = createMainWindow()
|
||||
|
||||
@@ -6,13 +6,13 @@ const { contextBridge, ipcRenderer } = require('electron')
|
||||
// expose an API to choose available devices
|
||||
contextBridge.exposeInMainWorld('nanodevices', {
|
||||
list_devices() {
|
||||
ipcRenderer.invoke('nanodevices:list_devices');
|
||||
return ipcRenderer.invoke('nanodevices:list_devices');
|
||||
},
|
||||
connect(deviceid) {
|
||||
ipcRenderer.invoke('nanodevices:connect', deviceid);
|
||||
return ipcRenderer.invoke('nanodevices:connect', deviceid);
|
||||
},
|
||||
disconnect(deviceid) {
|
||||
ipcRenderer.invoke('nanodevices:disconnect', deviceid);
|
||||
return ipcRenderer.invoke('nanodevices:disconnect', deviceid);
|
||||
},
|
||||
on_event(eventid_filter, callback) {
|
||||
console.log("attaching filter for ", eventid_filter);
|
||||
@@ -21,7 +21,10 @@ contextBridge.exposeInMainWorld('nanodevices', {
|
||||
if (eventid_filter=="*" || eventid_filter==eventid) {
|
||||
callback(eventid, deviceid, ...data);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
send(deviceid, jsonstr) {
|
||||
return ipcRenderer.invoke('nanodevices:send', deviceid, jsonstr);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -50,10 +50,10 @@ app.use(i18n)
|
||||
|
||||
app.config.globalProperties.window = window
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
|
||||
// TODO remove this
|
||||
window.nanodevices.on_event("*", (eventid, deviceid, ...data) => {
|
||||
console.log("Event on window ", eventid, deviceid, data)
|
||||
});
|
||||
window.nanodevices.list_devices();
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
|
||||
83
src/store.js
83
src/store.js
@@ -15,9 +15,11 @@ const ajv = new Ajv()
|
||||
export const useStore = defineStore('main', {
|
||||
state: () => {
|
||||
return {
|
||||
devices: {},
|
||||
connected: false, // TODO make into getter
|
||||
connectedId: null,
|
||||
profileCategories: [],
|
||||
selectedProfileId: null,
|
||||
connected: false,
|
||||
connectedDevices: ['test1', 'test2'],
|
||||
selectedFeature: 'knob',
|
||||
selectedKey: 'a',
|
||||
@@ -41,6 +43,13 @@ export const useStore = defineStore('main', {
|
||||
},
|
||||
previewDeviceModel: 'nanoOne',
|
||||
screenOrientation: 90,
|
||||
|
||||
// device state as received from the device
|
||||
keyState: "abcd",
|
||||
turns: 0,
|
||||
angle: 0,
|
||||
velocity: 0,
|
||||
last_event: 0
|
||||
}
|
||||
}, getters: {
|
||||
profiles: (state) => state.profileCategories.flatMap(c => c.profiles),
|
||||
@@ -50,6 +59,8 @@ export const useStore = defineStore('main', {
|
||||
currentConfigComponent: (state) => state.configPages[state.selectedFeature][state.currentConfigPage]?.component || WIP,
|
||||
currentConfigPages: (state) => state.configPages[state.selectedFeature] || {},
|
||||
multipleDevicesConnected: (state) => state.connectedDevices.length > 1,
|
||||
numAttachedDevices: (state) => Object.keys(state.devices).length,
|
||||
// connected: (state) => state.connectedId !== null,
|
||||
}, actions: {
|
||||
selectProfile(id) {
|
||||
if (!this.profileIds.includes(id)) return false
|
||||
@@ -166,6 +177,76 @@ export const useStore = defineStore('main', {
|
||||
cycleScreenOrientation() {
|
||||
this.screenOrientation = (this.screenOrientation + 90) % 360
|
||||
},
|
||||
|
||||
|
||||
// devices, device attachment, connection, and disconnection
|
||||
init_devices(ids) {
|
||||
console.log("Initializing devices: ", ids);
|
||||
for (let id of ids)
|
||||
this.update_devices(id, true);
|
||||
if (Object.keys(this.devices).length == 1) {
|
||||
// TODO auto-connect to the device
|
||||
let deviceid = Object.keys(this.devices)[0];
|
||||
console.log("Auto-connecting to device ", deviceid);
|
||||
window.nanodevices.connect(deviceid);
|
||||
}
|
||||
},
|
||||
update_devices(deviceid, attached) {
|
||||
if (attached) {
|
||||
if (!this.devices.hasOwnProperty(deviceid))
|
||||
this.devices[deviceid] = { serialNumber: deviceid, connected: false };
|
||||
}
|
||||
else {
|
||||
if (this.devices.hasOwnProperty(deviceid))
|
||||
delete this.devices[deviceid]; // TODO maybe mark as detached instead of deleting? then we can remember its name, etc...
|
||||
}
|
||||
},
|
||||
device_attached(deviceid) {
|
||||
this.update_devices(deviceid, true);
|
||||
if (Object.keys(this.devices).length == 1) {
|
||||
// TODO auto-connect to the device
|
||||
console.log("Auto-connecting to device ", deviceid);
|
||||
window.nanodevices.connect(deviceid);
|
||||
}
|
||||
},
|
||||
device_detached(deviceid) {
|
||||
if (this.devices[deviceid].connected) {
|
||||
// detached event arrived before disconnected event?
|
||||
this.devices[deviceid].connected = false;
|
||||
this.connected = false;
|
||||
}
|
||||
this.update_devices(deviceid, false);
|
||||
},
|
||||
device_connected(deviceid) {
|
||||
this.devices[deviceid].connected = true;
|
||||
this.connected = true;
|
||||
this.connectedId = deviceid;
|
||||
// TODO load profiles from device
|
||||
},
|
||||
device_disconnected(deviceid) {
|
||||
this.devices[deviceid].connected = false;
|
||||
this.connected = false;
|
||||
this.connectedId = null;
|
||||
// TODO switch UI to disconnected state
|
||||
},
|
||||
|
||||
// device events
|
||||
update_knob_position(turns, angle, velocity) {
|
||||
this.turns = turns;
|
||||
this.angle = angle;
|
||||
this.velocity = velocity;
|
||||
this.last_event = Date.now();
|
||||
},
|
||||
update_keystate(keystate) {
|
||||
this.keyState = keystate;
|
||||
this.last_event = Date.now();
|
||||
},
|
||||
|
||||
// settings changes
|
||||
update_device_name(name) {
|
||||
this.devices[this.connectedId].name = name;
|
||||
},
|
||||
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user