Skip to content

Commit 96e9687

Browse files
ert78gbmondalaci
authored andcommitted
feat(device): Read user config from eeprom (#413)
* feat(device): Read user config from eeprom * read data from eeprom * fix user config serialization * fix device connected detection * not allow override default config is eeprom is empty * add error handling to eeprom parsing * colorize log output * add USB[T] feature * add class name to USB[T] log * remove redundant error log msg * Add USB[T] to Apply user config
1 parent d621b1e commit 96e9687

File tree

11 files changed

+185
-33
lines changed

11 files changed

+185
-33
lines changed

packages/uhk-agent/src/services/device.service.ts

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ import { UhkHidDeviceService } from './uhk-hid-device.service';
1818
* UHK USB Communications command. All communication package should have start with a command code.
1919
*/
2020
enum Command {
21+
GetProperty = 0,
2122
UploadConfig = 8,
2223
ApplyConfig = 9,
2324
LaunchEepromTransfer = 12,
25+
ReadUserConfig = 15,
2426
GetKeyboardState = 16
2527
}
2628

@@ -31,13 +33,23 @@ enum EepromTransfer {
3133
WriteUserConfig = 3
3234
}
3335

36+
enum SystemPropertyIds {
37+
UsbProtocolVersion = 0,
38+
BridgeProtocolVersion = 1,
39+
DataModelVersion = 2,
40+
FirmwareVersion = 3,
41+
HardwareConfigSize = 4,
42+
UserConfigSize = 5
43+
}
44+
3445
const snooze = ms => new Promise(resolve => setTimeout(resolve, ms));
3546

3647
/**
3748
* IpcMain pair of the UHK Communication
3849
* Functionality:
3950
* - Detect device is connected or not
4051
* - Send UserConfiguration to the UHK Device
52+
* - Read UserConfiguration from the UHK Device
4153
*/
4254
export class DeviceService {
4355
private pollTimer$: Subscription;
@@ -48,6 +60,7 @@ export class DeviceService {
4860
private device: UhkHidDeviceService) {
4961
this.pollUhkDevice();
5062
ipcMain.on(IpcEvents.device.saveUserConfiguration, this.saveUserConfiguration.bind(this));
63+
ipcMain.on(IpcEvents.device.loadUserConfiguration, this.loadUserConfiguration.bind(this));
5164
logService.debug('[DeviceService] init success');
5265
}
5366

@@ -59,6 +72,38 @@ export class DeviceService {
5972
return this.connected;
6073
}
6174

75+
/**
76+
* Return with the actual UserConfiguration from UHK Device
77+
* @returns {Promise<Buffer>}
78+
*/
79+
public async loadUserConfiguration(event: Electron.Event): Promise<void> {
80+
let response = [];
81+
82+
try {
83+
this.logService.debug('[DeviceService] USB[T]: Read user configuration size from keyboard');
84+
const configSize = await this.getUserConfigSizeFromKeyboard();
85+
const chunkSize = 63;
86+
let offset = 0;
87+
let configBuffer = new Buffer(0);
88+
89+
this.logService.debug('[DeviceService] USB[T]: Read user configuration from keyboard');
90+
while (offset < configSize) {
91+
const chunkSizeToRead = Math.min(chunkSize, configSize - offset);
92+
const writeBuffer = Buffer.from([Command.ReadUserConfig, chunkSizeToRead, offset & 0xff, offset >> 8]);
93+
const readBuffer = await this.device.write(writeBuffer);
94+
configBuffer = Buffer.concat([configBuffer, new Buffer(readBuffer.slice(1, chunkSizeToRead + 1))]);
95+
offset += chunkSizeToRead;
96+
}
97+
response = UhkHidDeviceService.convertBufferToIntArray(configBuffer);
98+
} catch (error) {
99+
this.logService.error('[DeviceService] getUserConfigFromEeprom error', error);
100+
} finally {
101+
this.device.close();
102+
}
103+
104+
event.sender.send(IpcEvents.device.loadUserConfigurationReply, JSON.stringify(response));
105+
}
106+
62107
/**
63108
* HID API not support device attached and detached event.
64109
* This method check the keyboard is attached to the computer or not.
@@ -76,25 +121,38 @@ export class DeviceService {
76121
.do((connected: boolean) => {
77122
this.connected = connected;
78123
this.win.webContents.send(IpcEvents.device.deviceConnectionStateChanged, connected);
79-
this.logService.info(`Device connection state changed to: ${connected}`);
124+
this.logService.info(`[DeviceService] Device connection state changed to: ${connected}`);
80125
})
81126
.subscribe();
82127
}
83128

129+
/**
130+
* Return the UserConfiguration size from the UHK Device
131+
* @returns {Promise<number>}
132+
*/
133+
private async getUserConfigSizeFromKeyboard(): Promise<number> {
134+
const buffer = await this.device.write(new Buffer([Command.GetProperty, SystemPropertyIds.UserConfigSize]));
135+
const configSize = buffer[1] + (buffer[2] << 8);
136+
this.logService.debug('[DeviceService] User config size:', configSize);
137+
return configSize;
138+
}
139+
84140
private async saveUserConfiguration(event: Electron.Event, json: string): Promise<void> {
85141
const response = new IpcResponse();
86142

87143
try {
88-
this.sendUserConfigToKeyboard(json);
144+
this.logService.debug('[DeviceService] USB[T]: Write user configuration to keyboard');
145+
await this.sendUserConfigToKeyboard(json);
146+
this.logService.debug('[DeviceService] USB[T]: Write user configuration to EEPROM');
89147
await this.writeUserConfigToEeprom();
90-
this.device.close();
91148

92149
response.success = true;
93-
this.logService.info('transferring finished');
94150
}
95151
catch (error) {
96152
this.logService.error('[DeviceService] Transferring error', error);
97153
response.error = {message: error.message};
154+
} finally {
155+
this.device.close();
98156
}
99157

100158
event.sender.send(IpcEvents.device.saveUserConfigurationReply, response);
@@ -114,19 +172,14 @@ export class DeviceService {
114172
for (const fragment of fragments) {
115173
await this.device.write(fragment);
116174
}
117-
175+
this.logService.debug('[DeviceService] USB[T]: Apply user configuration to keyboard');
118176
const applyBuffer = new Buffer([Command.ApplyConfig]);
119177
await this.device.write(applyBuffer);
120-
this.logService.info('[DeviceService] Transferring finished');
121178
}
122179

123180
private async writeUserConfigToEeprom(): Promise<void> {
124-
this.logService.info('[DeviceService] Start write user configuration to eeprom');
125-
126-
const buffer = await this.device.write(new Buffer([Command.LaunchEepromTransfer, EepromTransfer.WriteUserConfig]));
181+
await this.device.write(new Buffer([Command.LaunchEepromTransfer, EepromTransfer.WriteUserConfig]));
127182
await this.waitUntilKeyboardBusy();
128-
129-
this.logService.info('[DeviceService] End write user configuration to eeprom');
130183
}
131184

132185
private async waitUntilKeyboardBusy(): Promise<void> {

packages/uhk-agent/src/services/uhk-hid-device.service.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@ import { Constants, LogService } from 'uhk-common';
66
* HID API wrapper to support unified logging and async write
77
*/
88
export class UhkHidDeviceService {
9+
/**
10+
* Convert the Buffer to number[]
11+
* @param {Buffer} buffer
12+
* @returns {number[]}
13+
* @private
14+
* @static
15+
*/
16+
public static convertBufferToIntArray(buffer: Buffer): number[] {
17+
return Array.prototype.slice.call(buffer, 0);
18+
}
19+
920
/**
1021
* Create the communication package that will send over USB and
1122
* - add usb report code as 1st byte
@@ -32,17 +43,6 @@ export class UhkHidDeviceService {
3243
return data;
3344
}
3445

35-
/**
36-
* Convert the Buffer to number[]
37-
* @param {Buffer} buffer
38-
* @returns {number[]}
39-
* @private
40-
* @static
41-
*/
42-
private static convertBufferToIntArray(buffer: Buffer): number[] {
43-
return Array.prototype.slice.call(buffer, 0);
44-
}
45-
4646
/**
4747
* Convert buffer to space separated hexadecimal string
4848
* @param {Buffer} buffer
@@ -109,7 +109,7 @@ export class UhkHidDeviceService {
109109
return reject(err);
110110
}
111111
const logString = UhkHidDeviceService.bufferToString(receivedData);
112-
this.logService.debug('[UhkHidDevice] Transfer UHK ===> Agent: ', logString);
112+
this.logService.debug('[UhkHidDevice] USB[R]:', logString);
113113

114114
if (receivedData[0] !== 0) {
115115
return reject(new Error(`Communications error with UHK. Response code: ${receivedData[0]}`));
@@ -119,7 +119,7 @@ export class UhkHidDeviceService {
119119
});
120120

121121
const sendData = UhkHidDeviceService.getTransferData(buffer);
122-
this.logService.debug('[UhkHidDevice] Transfer Agent ===> UHK: ', UhkHidDeviceService.bufferToString(sendData));
122+
this.logService.debug('[UhkHidDevice] USB[W]:', UhkHidDeviceService.bufferToString(sendData));
123123
device.write(sendData);
124124
});
125125
}

packages/uhk-common/src/util/ipcEvents.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class Device {
2222
public static readonly deviceConnectionStateChanged = 'device-connection-state-changed';
2323
public static readonly saveUserConfiguration = 'device-save-user-configuration';
2424
public static readonly saveUserConfigurationReply = 'device-save-user-configuration-reply';
25+
public static readonly loadUserConfiguration = 'device-load-user-configuration';
26+
public static readonly loadUserConfigurationReply = 'device-load-user-configuration-reply';
2527
}
2628

2729
export class IpcEvents {

packages/uhk-web/src/app/services/device-renderer.service.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
SaveConfigurationReplyAction,
1010
SetPrivilegeOnLinuxReplyAction
1111
} from '../store/actions/device';
12+
import { LoadUserConfigFromDeviceReplyAction } from '../store/actions/user-config';
1213

1314
@Injectable()
1415
export class DeviceRendererService {
@@ -28,6 +29,10 @@ export class DeviceRendererService {
2829
this.ipcRenderer.send(IpcEvents.device.saveUserConfiguration, JSON.stringify(buffer));
2930
}
3031

32+
loadUserConfiguration(): void {
33+
this.ipcRenderer.send(IpcEvents.device.loadUserConfiguration);
34+
}
35+
3136
private registerEvents(): void {
3237
this.ipcRenderer.on(IpcEvents.device.deviceConnectionStateChanged, (event: string, arg: boolean) => {
3338
this.dispachStoreAction(new ConnectionStateChangedAction(arg));
@@ -40,6 +45,10 @@ export class DeviceRendererService {
4045
this.ipcRenderer.on(IpcEvents.device.saveUserConfigurationReply, (event: string, response: IpcResponse) => {
4146
this.dispachStoreAction(new SaveConfigurationReplyAction(response));
4247
});
48+
49+
this.ipcRenderer.on(IpcEvents.device.loadUserConfigurationReply, (event: string, response: string) => {
50+
this.dispachStoreAction(new LoadUserConfigFromDeviceReplyAction(JSON.parse(response)));
51+
});
4352
}
4453

4554
private dispachStoreAction(action: Action): void {

packages/uhk-web/src/app/store/actions/user-config.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const PREFIX = '[user-config] ';
88
// tslint:disable-next-line:variable-name
99
export const ActionTypes = {
1010
LOAD_USER_CONFIG: type(PREFIX + 'Load User Config'),
11+
LOAD_USER_CONFIG_FROM_DEVICE: type(PREFIX + 'Load User Config from Device'),
12+
LOAD_USER_CONFIG_FROM_DEVICE_REPLY: type(PREFIX + 'Load User Config from Device reply'),
1113
LOAD_USER_CONFIG_SUCCESS: type(PREFIX + 'Load User Config Success'),
1214
SAVE_USER_CONFIG_SUCCESS: type(PREFIX + 'Save User Config Success')
1315
};
@@ -16,6 +18,16 @@ export class LoadUserConfigAction implements Action {
1618
type = ActionTypes.LOAD_USER_CONFIG;
1719
}
1820

21+
export class LoadUserConfigFromDeviceAction implements Action {
22+
type = ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE;
23+
}
24+
25+
export class LoadUserConfigFromDeviceReplyAction implements Action {
26+
type = ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE_REPLY;
27+
28+
constructor(public payload: Array<number>) { }
29+
}
30+
1931
export class LoadUserConfigSuccessAction implements Action {
2032
type = ActionTypes.LOAD_USER_CONFIG_SUCCESS;
2133

packages/uhk-web/src/app/store/effects/app.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export class ApplicationEffects {
3030
this.logService.info('Renderer appStart effect end');
3131
});
3232

33-
@Effect({ dispatch: false })
33+
@Effect({dispatch: false})
3434
showNotification$: Observable<Action> = this.actions$
3535
.ofType(ActionTypes.APP_SHOW_NOTIFICATION)
3636
.map(toPayload)

packages/uhk-web/src/app/store/effects/device.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ import 'rxjs/add/operator/withLatestFrom';
1515
import { NotificationType, IpcResponse } from 'uhk-common';
1616
import {
1717
ActionTypes,
18-
ConnectionStateChangedAction, HideSaveToKeyboardButton,
18+
ConnectionStateChangedAction,
19+
HideSaveToKeyboardButton,
1920
PermissionStateChangedAction,
2021
SaveToKeyboardSuccessAction,
2122
SaveToKeyboardSuccessFailed
@@ -25,10 +26,11 @@ import { ShowNotificationAction } from '../actions/app';
2526
import { AppState } from '../index';
2627
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
2728
import { UhkBuffer } from '../../config-serializer/uhk-buffer';
29+
import { LoadUserConfigFromDeviceAction } from '../actions/user-config';
2830

2931
@Injectable()
3032
export class DeviceEffects {
31-
@Effect({dispatch: false})
33+
@Effect()
3234
deviceConnectionStateChange$: Observable<Action> = this.actions$
3335
.ofType(ActionTypes.CONNECTION_STATE_CHANGED)
3436
.map(toPayload)
@@ -39,6 +41,13 @@ export class DeviceEffects {
3941
else {
4042
this.router.navigate(['/detection']);
4143
}
44+
})
45+
.switchMap((connected: boolean) => {
46+
if (connected) {
47+
return Observable.of(new LoadUserConfigFromDeviceAction());
48+
}
49+
50+
return Observable.empty();
4251
});
4352

4453
@Effect({dispatch: false})

packages/uhk-web/src/app/store/effects/user-config.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import 'rxjs/add/operator/withLatestFrom';
1111
import 'rxjs/add/operator/mergeMap';
1212
import 'rxjs/add/observable/of';
1313

14-
import { NotificationType } from 'uhk-common';
14+
import { LogService, NotificationType } from 'uhk-common';
1515

1616
import {
1717
ActionTypes,
@@ -29,6 +29,8 @@ import { MacroActions } from '../actions/macro';
2929
import { UndoUserConfigData } from '../../models/undo-user-config-data';
3030
import { ShowNotificationAction, DismissUndoNotificationAction } from '../actions/app';
3131
import { ShowSaveToKeyboardButtonAction } from '../actions/device';
32+
import { DeviceRendererService } from '../../services/device-renderer.service';
33+
import { UhkBuffer } from '../../config-serializer/uhk-buffer';
3234

3335
@Injectable()
3436
export class UserConfigEffects {
@@ -86,10 +88,46 @@ export class UserConfigEffects {
8688
return [new LoadUserConfigSuccessAction(config), go(payload.path)];
8789
});
8890

91+
@Effect({dispatch: false}) loadUserConfigFromDevice$ = this.actions$
92+
.ofType(ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE)
93+
.do(() => this.deviceRendererService.loadUserConfiguration());
94+
95+
@Effect() loadUserConfigFromDeviceReply$ = this.actions$
96+
.ofType(ActionTypes.LOAD_USER_CONFIG_FROM_DEVICE_REPLY)
97+
.map(action => action.payload)
98+
.switchMap((data: Array<number>) => {
99+
try {
100+
let userConfig;
101+
if (data.length > 0) {
102+
const uhkBuffer = new UhkBuffer();
103+
let hasNonZeroValue = false;
104+
for (const num of data) {
105+
if (num > 0) {
106+
hasNonZeroValue = true;
107+
}
108+
uhkBuffer.writeUInt8(num);
109+
}
110+
uhkBuffer.offset = 0;
111+
userConfig = new UserConfiguration();
112+
userConfig.fromBinary(uhkBuffer);
113+
114+
if (hasNonZeroValue) {
115+
return Observable.of(new LoadUserConfigSuccessAction(userConfig));
116+
}
117+
}
118+
} catch (err) {
119+
this.logService.error('Eeprom parse error:', err);
120+
}
121+
122+
return Observable.empty();
123+
});
124+
89125
constructor(private actions$: Actions,
90126
private dataStorageRepository: DataStorageRepositoryService,
91127
private store: Store<AppState>,
92-
private defaultUserConfigurationService: DefaultUserConfigurationService) {
128+
private defaultUserConfigurationService: DefaultUserConfigurationService,
129+
private deviceRendererService: DeviceRendererService,
130+
private logService: LogService) {
93131
}
94132

95133
private getUserConfiguration() {

0 commit comments

Comments
 (0)