feat(config): Read / write hardware configuration area (#423)
* add write-hca.js * refactor: Move config serializer into the uhk-common package * refactor: Move getTransferBuffers into the uhk-usb package * refactor: delete obsoleted classes * build: add uhk-usb build command * refactor: move eeprom transfer to uhk-usb package * fix: Fix write-hca.js * feat: load hardware config from the device and * style: fix ts lint errors * build: fix rxjs dependency resolve * test: Add jasmine unit test framework to the tet serializer * fix(user-config): A "type": "basic", properties to the "keystroke" action types * feat(usb): set chmod+x on write-hca.js * feat(usb): Create USB logger * style: Fix type * build: Add chalk to dependencies. Chalk will colorize the output
This commit is contained in:
committed by
László Monda
parent
1122784bdb
commit
9294bede50
@@ -1,6 +1,6 @@
|
||||
import * as storage from 'electron-settings';
|
||||
|
||||
import { UserConfiguration } from '../../app/config-serializer/config-items/user-configuration';
|
||||
import { UserConfiguration } from 'uhk-common';
|
||||
import { DataStorageRepositoryService } from '../../app/services/datastorage-repository.service';
|
||||
import { AutoUpdateSettings } from '../../app/models/auto-update-settings';
|
||||
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import * as log from 'electron-log';
|
||||
import * as util from 'util';
|
||||
|
||||
import { LogService } from 'uhk-common';
|
||||
|
||||
const transferRegExp = /USB\[T]:/;
|
||||
const writeRegExp = /USB\[W]:/;
|
||||
const readRegExp = /USB\[R]: 00/;
|
||||
const errorRegExp = /(?:(USB\[R]: ([^0]|0[^0])))/;
|
||||
import { LogService, LogRegExps } from 'uhk-common';
|
||||
|
||||
// https://github.com/megahertz/electron-log/issues/44
|
||||
// console.debug starting with Chromium 58 this method is a no-op on Chromium browsers.
|
||||
if (console.debug) {
|
||||
console.debug = (...args: any[]): void => {
|
||||
if (writeRegExp.test(args[0])) {
|
||||
if (LogRegExps.writeRegExp.test(args[0])) {
|
||||
console.log('%c' + args[0], 'color:blue');
|
||||
} else if (readRegExp.test(args[0])) {
|
||||
} else if (LogRegExps.readRegExp.test(args[0])) {
|
||||
console.log('%c' + args[0], 'color:green');
|
||||
} else if (errorRegExp.test(args[0])) {
|
||||
} else if (LogRegExps.errorRegExp.test(args[0])) {
|
||||
console.log('%c' + args[0], 'color:red');
|
||||
}else if (transferRegExp.test(args[0])) {
|
||||
}else if (LogRegExps.transferRegExp.test(args[0])) {
|
||||
console.log('%c' + args[0], 'color:orange');
|
||||
} else {
|
||||
console.log(...args);
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { LogService } from 'uhk-common';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
import { UhkHidApiService } from './uhk-hid-api.service';
|
||||
|
||||
export function uhkDeviceFactory(logService: LogService): UhkDeviceService {
|
||||
// HID API officially support MAC, WIN and linux x64 platform
|
||||
// https://github.com/node-hid/node-hid#platform-support
|
||||
if (process.platform === 'darwin' ||
|
||||
process.platform === 'win32' ||
|
||||
(process.platform === 'linux' && process.arch === 'x64')) {
|
||||
return new UhkHidApiService(logService);
|
||||
}
|
||||
|
||||
// On other platform use libUsb, but we try to test on all platform
|
||||
// return new UhkLibUsbApiService(logService);
|
||||
return new UhkHidApiService(logService);
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
import { OnDestroy } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Observer } from 'rxjs/Observer';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Constants, LogService } from 'uhk-common';
|
||||
import { SenderMessage } from '../models/sender-message';
|
||||
|
||||
enum Command {
|
||||
UploadConfig = 8,
|
||||
ApplyConfig = 9
|
||||
}
|
||||
|
||||
export abstract class UhkDeviceService implements OnDestroy {
|
||||
protected connected$: BehaviorSubject<boolean>;
|
||||
protected initialized$: BehaviorSubject<boolean>;
|
||||
protected deviceOpened$: BehaviorSubject<boolean>;
|
||||
protected outSubscription: Subscription;
|
||||
|
||||
protected messageIn$: Observable<Buffer>;
|
||||
protected messageOut$: Subject<SenderMessage>;
|
||||
|
||||
constructor(protected logService: LogService) {
|
||||
this.messageOut$ = new Subject<SenderMessage>();
|
||||
this.initialized$ = new BehaviorSubject(false);
|
||||
this.connected$ = new BehaviorSubject(false);
|
||||
this.deviceOpened$ = new BehaviorSubject(false);
|
||||
this.outSubscription = Subscription.EMPTY;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.disconnect();
|
||||
this.initialized$.unsubscribe();
|
||||
this.connected$.unsubscribe();
|
||||
this.deviceOpened$.unsubscribe();
|
||||
}
|
||||
|
||||
sendConfig(configBuffer: Buffer): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
this.logService.info('Sending...', configBuffer);
|
||||
const fragments: Buffer[] = [];
|
||||
const MAX_SENDING_PAYLOAD_SIZE = Constants.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();
|
||||
this.logService.info('Sending finished');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fragments
|
||||
.map<SenderMessage>(fragment => ({ buffer: fragment, observer }))
|
||||
.forEach(senderPackage => this.messageOut$.next(senderPackage));
|
||||
});
|
||||
}
|
||||
|
||||
applyConfig(): Observable<Buffer> {
|
||||
return Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
this.logService.info('Applying configuration');
|
||||
this.messageOut$.next({
|
||||
buffer: new Buffer([Command.ApplyConfig]),
|
||||
observer: subscriber
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
isInitialized(): Observable<boolean> {
|
||||
return this.initialized$.asObservable();
|
||||
}
|
||||
|
||||
isConnected(): Observable<boolean> {
|
||||
return this.connected$.asObservable();
|
||||
}
|
||||
|
||||
isOpened(): Observable<boolean> {
|
||||
return this.deviceOpened$.asObservable();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.outSubscription.unsubscribe();
|
||||
this.messageIn$ = undefined;
|
||||
this.initialized$.next(false);
|
||||
this.deviceOpened$.next(false);
|
||||
this.connected$.next(false);
|
||||
}
|
||||
|
||||
abstract initialize(): void;
|
||||
|
||||
abstract hasPermissions(): Observable<boolean>;
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
import { Injectable, OnDestroy } from '@angular/core';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subscriber } from 'rxjs/Subscriber';
|
||||
import { Device, devices, HID } from 'node-hid';
|
||||
|
||||
import 'rxjs/add/observable/empty';
|
||||
import 'rxjs/add/observable/interval';
|
||||
import 'rxjs/add/operator/catch';
|
||||
import 'rxjs/add/operator/concat';
|
||||
import 'rxjs/add/operator/combineLatest';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/publish';
|
||||
import 'rxjs/add/operator/do';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
import { Constants, LogService } from 'uhk-common';
|
||||
import { UhkDeviceService } from './uhk-device.service';
|
||||
|
||||
@Injectable()
|
||||
export class UhkHidApiService extends UhkDeviceService implements OnDestroy {
|
||||
private device: HID;
|
||||
|
||||
constructor(protected logService: LogService) {
|
||||
super(logService);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
if (this.initialized$.getValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.device = this.getDevice();
|
||||
if (!this.device) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deviceOpened$.next(true);
|
||||
|
||||
this.messageIn$ = Observable.create((subscriber: Subscriber<Buffer>) => {
|
||||
this.logService.info('Try to read');
|
||||
this.device.read((error: any, data: any = []) => {
|
||||
if (error) {
|
||||
this.logService.error('reading error', error);
|
||||
subscriber.error(error);
|
||||
} else {
|
||||
this.logService.info('read data', data);
|
||||
subscriber.next(data);
|
||||
subscriber.complete();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const outSending = this.messageOut$.concatMap(senderPackage => {
|
||||
return (<Observable<void>>Observable.create((subscriber: Subscriber<void>) => {
|
||||
this.logService.info('transfering', senderPackage.buffer);
|
||||
const data = Array.prototype.slice.call(senderPackage.buffer, 0);
|
||||
// if data start with 0 need to add additional leading zero because HID API remove it.
|
||||
// https://github.com/node-hid/node-hid/issues/187
|
||||
if (data.length > 0 && data[0] === 0) {
|
||||
data.unshift(0);
|
||||
}
|
||||
|
||||
// From HID API documentation:
|
||||
// http://www.signal11.us/oss/hidapi/hidapi/doxygen/html/group__API.html#gad14ea48e440cf5066df87cc6488493af
|
||||
// The first byte of data[] must contain the Report ID.
|
||||
// For devices which only support a single report, this must be set to 0x0.
|
||||
data.unshift(0);
|
||||
|
||||
try {
|
||||
this.device.write(data);
|
||||
this.logService.info('transfering finished');
|
||||
subscriber.complete();
|
||||
}
|
||||
catch (error) {
|
||||
this.logService.error('transfering errored', error);
|
||||
subscriber.error(error);
|
||||
}
|
||||
})).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.initialized$.next(true);
|
||||
}
|
||||
|
||||
hasPermissions(): Observable<boolean> {
|
||||
return this.isConnected()
|
||||
.combineLatest(this.deviceOpened$)
|
||||
.map((latest: boolean[]) => {
|
||||
const connected = latest[0];
|
||||
const opened = latest[1];
|
||||
if (!connected) {
|
||||
return false;
|
||||
} else if (opened) {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the 0 interface of the keyboard.
|
||||
* @returns {HID}
|
||||
*/
|
||||
getDevice(): HID {
|
||||
try {
|
||||
const devs = devices();
|
||||
this.logService.info('Available devices:', devs);
|
||||
|
||||
const dev = devs.find((x: Device) =>
|
||||
x.vendorId === Constants.VENDOR_ID &&
|
||||
x.productId === Constants.PRODUCT_ID &&
|
||||
((x.usagePage === 128 && x.usage === 129) || x.interface === 0));
|
||||
|
||||
const device = new HID(dev.path);
|
||||
this.logService.info('Used device:', dev);
|
||||
return device;
|
||||
}
|
||||
catch (err) {
|
||||
this.logService.error('Can not create device:', err);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user