Transfer the layer/keymap/config with node-usb on shortcuts
This commit is contained in:
committed by
József Farkas
parent
4ed81331b3
commit
517aed1b1c
19
.travis.yml
19
.travis.yml
@@ -1,10 +1,16 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
sudo: required
|
||||
|
||||
node_js:
|
||||
- '5.10.0'
|
||||
- '6.9.4'
|
||||
|
||||
before_install:
|
||||
- sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y
|
||||
- sudo apt-get update -q
|
||||
- sudo apt-get install g++-4.8 -y
|
||||
|
||||
install:
|
||||
- npm install -g npm@3.10.7
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
@@ -19,3 +25,12 @@ script:
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
env:
|
||||
- CXX=g++-4.8
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- build-essential
|
||||
- libudev-dev
|
||||
|
||||
@@ -14,8 +14,10 @@
|
||||
"@types/electron": "^1.4.29",
|
||||
"@types/jquery": "2.0.39",
|
||||
"@types/node": "6.0.52",
|
||||
"@types/usb": "^1.1.2",
|
||||
"copy-webpack-plugin": "^4.0.1",
|
||||
"electron": "1.4.15",
|
||||
"electron-rebuild": "^1.5.5",
|
||||
"expose-loader": "^0.7.1",
|
||||
"html-loader": "0.4.4",
|
||||
"node-sass": "^4.3.0",
|
||||
@@ -54,10 +56,12 @@
|
||||
"rxjs": "5.0.3",
|
||||
"select2": "^4.0.3",
|
||||
"typescript": "2.1.5",
|
||||
"usb": "git+https://github.com/aktary/node-usb.git",
|
||||
"xml-loader": "^1.1.0",
|
||||
"zone.js": "0.7.6"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "electron-rebuild -w usb -p",
|
||||
"tslint": "tslint \"src/**/*.ts\" \"test-serializer/**/*.ts\"",
|
||||
"stylelint": "stylelint \"src/**/*.scss\" --syntax scss",
|
||||
"lint": "run-s -scn tslint stylelint",
|
||||
|
||||
@@ -64,6 +64,7 @@ import { CancelableDirective } from './directives';
|
||||
|
||||
import { CaptureService } from './services/capture.service';
|
||||
import { MapperService } from './services/mapper.service';
|
||||
import { UhkDeviceService } from './services/uhk-device.service';
|
||||
|
||||
import { KeymapEffects, MacroEffects } from './store/effects';
|
||||
import { keymapReducer, macroReducer, presetReducer } from './store/reducers';
|
||||
@@ -156,7 +157,8 @@ const storeConfig = {
|
||||
appRoutingProviders,
|
||||
KeymapEditGuard,
|
||||
MacroNotFoundGuard,
|
||||
CaptureService
|
||||
CaptureService,
|
||||
UhkDeviceService
|
||||
],
|
||||
bootstrap: [MainAppComponent]
|
||||
})
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, HostListener, ViewChild } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
|
||||
import '@ngrx/core/add/operator/select';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import 'rxjs/add/operator/first';
|
||||
import 'rxjs/add/operator/let';
|
||||
import 'rxjs/add/operator/publishReplay';
|
||||
import 'rxjs/add/operator/switchMap';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
|
||||
import { Keymap } from '../../../config-serializer/config-items/Keymap';
|
||||
import { UhkBuffer } from '../../../config-serializer/UhkBuffer';
|
||||
import { AppState } from '../../../store';
|
||||
import { getKeymap, getKeymapEntities } from '../../../store/reducers/keymap';
|
||||
import { SvgKeyboardWrapComponent } from '../../svg/wrap';
|
||||
|
||||
import { UhkDeviceService } from '../../../services/uhk-device.service';
|
||||
|
||||
@Component({
|
||||
selector: 'keymap-edit',
|
||||
@@ -22,12 +27,16 @@ import { getKeymap, getKeymapEntities } from '../../../store/reducers/keymap';
|
||||
}
|
||||
})
|
||||
export class KeymapEditComponent {
|
||||
|
||||
@ViewChild(SvgKeyboardWrapComponent) wrap: SvgKeyboardWrapComponent;
|
||||
|
||||
private keymap$: Observable<Keymap>;
|
||||
private deletable$: Observable<boolean>;
|
||||
|
||||
constructor(
|
||||
private store: Store<AppState>,
|
||||
private route: ActivatedRoute
|
||||
private route: ActivatedRoute,
|
||||
private uhkDevice: UhkDeviceService
|
||||
) {
|
||||
this.keymap$ = route
|
||||
.params
|
||||
@@ -39,4 +48,58 @@ export class KeymapEditComponent {
|
||||
this.deletable$ = store.let(getKeymapEntities())
|
||||
.map((keymaps: Keymap[]) => keymaps.length > 1);
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.u', ['$event'])
|
||||
onCtrlU(event: KeyboardEvent): void {
|
||||
console.log('ctrl + u pressed');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.sendLayer();
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.i', ['$event'])
|
||||
onCtrlI(event: KeyboardEvent): void {
|
||||
console.log('ctrl + i pressed');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.sendKeymap();
|
||||
}
|
||||
|
||||
private sendLayer(): void {
|
||||
const currentLayer: number = this.wrap.getSelectedLayer();
|
||||
this.keymap$
|
||||
.first()
|
||||
.map(keymap => keymap.layers[currentLayer])
|
||||
.map(layer => {
|
||||
const uhkBuffer = new UhkBuffer();
|
||||
layer.toBinary(uhkBuffer);
|
||||
return uhkBuffer.getBufferContent();
|
||||
})
|
||||
.switchMap((buffer: Buffer) => this.uhkDevice.sendConfig(buffer))
|
||||
.do(response => console.log('Sending layer finished', response))
|
||||
.switchMap(() => this.uhkDevice.applyConfig())
|
||||
.subscribe(
|
||||
(response) => console.log('Applying layer finished', response),
|
||||
error => console.error('Error during uploading layer', error),
|
||||
() => console.log('Layer has been sucessfully uploaded')
|
||||
);
|
||||
}
|
||||
|
||||
private sendKeymap(): void {
|
||||
this.keymap$
|
||||
.first()
|
||||
.map(keymap => {
|
||||
const uhkBuffer = new UhkBuffer();
|
||||
keymap.toBinary(uhkBuffer);
|
||||
return uhkBuffer.getBufferContent();
|
||||
})
|
||||
.switchMap((buffer: Buffer) => this.uhkDevice.sendConfig(buffer))
|
||||
.do(response => console.log('Sending keymap finished', response))
|
||||
.switchMap(() => this.uhkDevice.applyConfig())
|
||||
.subscribe(
|
||||
(response) => console.log('Applying keymap finished', response),
|
||||
error => console.error('Error during uploading keymap', error),
|
||||
() => console.log('Keymap has been sucessfully uploaded')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,10 @@ export class SvgKeyboardWrapComponent implements OnInit, OnChanges {
|
||||
this.currentLayer = index;
|
||||
}
|
||||
|
||||
getSelectedLayer(): number {
|
||||
return this.currentLayer;
|
||||
}
|
||||
|
||||
private getKeyActionContent(keyAction: KeyAction): Observable<NameValuePair[]> {
|
||||
if (keyAction instanceof KeystrokeAction) {
|
||||
const keystrokeAction: KeystrokeAction = keyAction;
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
<link rel="shortcut icon" href="images/favicon.ico">
|
||||
<script src="polyfills.uhk.js"></script>
|
||||
<script src="vendor.uhk.js"></script>
|
||||
<!--<script src="vendor/usb/usb.js"></script>-->
|
||||
<script>
|
||||
usb = require('usb');
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Google Tag Manager -->
|
||||
|
||||
@@ -1,4 +1,16 @@
|
||||
import { Component, ViewEncapsulation } from '@angular/core';
|
||||
import { Component, ViewEncapsulation, HostListener } from '@angular/core';
|
||||
|
||||
// import { Observable } from 'rxjs/Observable';
|
||||
|
||||
// import { Store } from '@ngrx/store';
|
||||
// import { AppState } from '../store';
|
||||
// import { DataStorage } from '../store/storage';
|
||||
// import { getKeymapEntities, getMacroEntities } from '../store/reducers';
|
||||
|
||||
// import { UhkBuffer } from '../config-serializer/UhkBuffer';
|
||||
// import { UserConfiguration } from '../config-serializer/config-items/UserConfiguration';
|
||||
|
||||
// import { UhkDeviceService } from '../services/uhk-device.service';
|
||||
|
||||
@Component({
|
||||
selector: 'main-app',
|
||||
@@ -6,4 +18,50 @@ import { Component, ViewEncapsulation } from '@angular/core';
|
||||
styles: [require('./main-app.component.scss')],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class MainAppComponent { }
|
||||
export class MainAppComponent {
|
||||
|
||||
// private configuration$: Observable<UserConfiguration>;
|
||||
|
||||
constructor(
|
||||
// private uhkDevice: UhkDeviceService,
|
||||
// store: Store<AppState>,
|
||||
// dataStorage: DataStorage
|
||||
) {
|
||||
// this.configuration$ = store.let(getKeymapEntities())
|
||||
// .combineLatest(store.let(getMacroEntities()))
|
||||
// .map((pair) => {
|
||||
// const config = new UserConfiguration();
|
||||
// Object.assign(config, dataStorage.getConfiguration());
|
||||
// config.keymaps = pair[0];
|
||||
// config.macros = pair[1];
|
||||
// return config;
|
||||
// });
|
||||
}
|
||||
|
||||
@HostListener('window:keydown.control.o', ['$event'])
|
||||
onCtrlO(event: KeyboardEvent): void {
|
||||
console.log('ctrl + o pressed');
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.sendConfiguration();
|
||||
}
|
||||
|
||||
private sendConfiguration(): void {
|
||||
// this.configuration$
|
||||
// .first()
|
||||
// .map(configuration => {
|
||||
// const uhkBuffer = new UhkBuffer();
|
||||
// configuration.toBinary(uhkBuffer);
|
||||
// return uhkBuffer.getBufferContent();
|
||||
// })
|
||||
// .switchMap((buffer: Buffer) => this.uhkDevice.sendConfig(buffer))
|
||||
// .do(response => console.log('Sending config finished', response))
|
||||
// .switchMap(() => this.uhkDevice.applyConfig())
|
||||
// .subscribe(
|
||||
// (response) => console.log('Applying config finished', response),
|
||||
// error => console.error('Error during uploading config', error),
|
||||
// () => console.log('Config has been sucessfully uploaded')
|
||||
// );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
process.stdout = require('browser-stdout')();
|
||||
if (!process.stdout) {
|
||||
process.stdout = require('browser-stdout')();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
import 'core-js/es7/object';
|
||||
import 'core-js/es7/reflect';
|
||||
import 'zone.js/dist/zone';
|
||||
|
||||
159
src/services/uhk-device.service.ts
Normal file
159
src/services/uhk-device.service.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
import { ConnectableObservable } from 'rxjs/observable/ConnectableObservable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/from';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/mergeMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
|
||||
import { Device, Interface, InEndpoint, OutEndpoint, findByIds } from 'usb';
|
||||
|
||||
import { Layer } from '../config-serializer/config-items/Layer';
|
||||
import { UhkBuffer } from '../config-serializer/UhkBuffer';
|
||||
|
||||
const vendorId = 0x16d3;
|
||||
const productId = 0x05ea;
|
||||
const MAX_PAYLOAD_SIZE = 64;
|
||||
|
||||
enum Command {
|
||||
UploadConfig = 8,
|
||||
ApplyConfig = 9
|
||||
};
|
||||
|
||||
interface SenderMessage {
|
||||
buffer: Buffer;
|
||||
observer: Observer<any>;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class UhkDeviceService {
|
||||
|
||||
private device: Device;
|
||||
private connected: boolean;
|
||||
|
||||
private messageIn$: Observable<Buffer>;
|
||||
private messageOut$: Subject<SenderMessage>;
|
||||
|
||||
private outSubscription: Subscription;
|
||||
|
||||
constructor() {
|
||||
this.messageOut$ = new Subject<SenderMessage>();
|
||||
this.connect();
|
||||
}
|
||||
|
||||
connect(): void {
|
||||
if (this.connected) {
|
||||
return;
|
||||
}
|
||||
this.device = findByIds(vendorId, productId);
|
||||
this.device.open();
|
||||
|
||||
const usbInterface: Interface = this.device.interface(0);
|
||||
// https://github.com/tessel/node-usb/issues/147
|
||||
// The function 'isKernelDriverActive' is not available on Windows and not even needed.
|
||||
if (process.platform !== 'win32' && usbInterface.isKernelDriverActive()) {
|
||||
usbInterface.detachKernelDriver();
|
||||
}
|
||||
usbInterface.claim();
|
||||
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
const inEndPoint: InEndpoint = <InEndpoint>usbInterface.endpoints[0];
|
||||
console.log('Try to read');
|
||||
inEndPoint.transfer(MAX_PAYLOAD_SIZE, (error: string, receivedBuffer: Buffer) => {
|
||||
if (error) {
|
||||
console.error('reading error', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
console.log('read data', receivedBuffer);
|
||||
subscriber.next(receivedBuffer);
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const outEndPoint: OutEndpoint = <OutEndpoint>usbInterface.endpoints[1];
|
||||
const outSending = this.messageOut$.concatMap(senderPackage => {
|
||||
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
|
||||
console.log('transfering', senderPackage.buffer);
|
||||
outEndPoint.transfer(senderPackage.buffer, (error) => {
|
||||
if (error) {
|
||||
console.error('transfering errored', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
console.log('transfering finished');
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
})).concat(this.messageIn$)
|
||||
.do(buffer => senderPackage.observer.next(buffer) && senderPackage.observer.complete())
|
||||
.catch((error: string) => {
|
||||
senderPackage.observer.error(error);
|
||||
return Observable.empty<void>();
|
||||
});
|
||||
}).publish();
|
||||
this.outSubscription = outSending.connect();
|
||||
|
||||
this.connected = true;
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
if (!this.connected) {
|
||||
return;
|
||||
}
|
||||
this.outSubscription.unsubscribe();
|
||||
this.messageIn$ = undefined;
|
||||
this.device.interface(0).release();
|
||||
this.device.close();
|
||||
this.connected = false;
|
||||
}
|
||||
|
||||
sendConfig(configBuffer: Buffer): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
console.log('Sending...', configBuffer);
|
||||
const fragments: Buffer[] = [];
|
||||
const MAX_SENDING_PAYLOAD_SIZE = MAX_PAYLOAD_SIZE - 4;
|
||||
for (let offset = 0; offset < configBuffer.length; offset += MAX_SENDING_PAYLOAD_SIZE) {
|
||||
const length = offset + MAX_SENDING_PAYLOAD_SIZE < configBuffer.length
|
||||
? MAX_SENDING_PAYLOAD_SIZE
|
||||
: configBuffer.length - offset;
|
||||
const header = new Buffer([Command.UploadConfig, length, offset & 0xFF, offset >> 8]);
|
||||
fragments.push(Buffer.concat([header, configBuffer.slice(offset, offset + length)]));
|
||||
}
|
||||
|
||||
const buffers: Buffer[] = [];
|
||||
const observer: Observer<Buffer> = {
|
||||
next: (buffer: Buffer) => buffers.push(buffer),
|
||||
error: error => subscriber.error(error),
|
||||
complete: () => {
|
||||
if (buffers.length === fragments.length) {
|
||||
subscriber.next(Buffer.concat(buffers));
|
||||
subscriber.complete();
|
||||
console.log('Sending finished');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fragments
|
||||
.map<SenderMessage>(fragment => ({ buffer: fragment, observer }))
|
||||
.forEach(senderPackage => this.messageOut$.next(senderPackage));
|
||||
});
|
||||
}
|
||||
|
||||
applyConfig(): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
console.log('Applying configuration');
|
||||
this.messageOut$.next({
|
||||
buffer: new Buffer([Command.ApplyConfig]),
|
||||
observer: subscriber
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,7 +17,8 @@
|
||||
"jquery",
|
||||
"core-js",
|
||||
"select2",
|
||||
"electron"
|
||||
"electron",
|
||||
"usb"
|
||||
]
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
Reference in New Issue
Block a user