Skip to content

Commit

Permalink
feat: add UHL paired devices to the host connections
Browse files Browse the repository at this point in the history
  • Loading branch information
ert78gb committed Dec 15, 2024
1 parent 22b23f2 commit 0183735
Show file tree
Hide file tree
Showing 16 changed files with 223 additions and 23 deletions.
5 changes: 2 additions & 3 deletions packages/uhk-common/src/models/device-connection-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ export interface DeviceConnectionState {
halvesInfo: HalvesInfo;
hardwareModules?: HardwareModules;
/**
* Each element describe the hostConnection is the user-config are paired to the keyboard or not.
* If the value is 1 then paired.
* The BLE addresses of the devices that paired with the keyboard but not is the hostConnections of the user configuration.
*/
pairedDevices: number[];
newPairedDevices: string[];
udevRulesInfo: UdevRulesInfo;
}
46 changes: 37 additions & 9 deletions packages/uhk-usb/src/uhk-hid-device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import {
BLE_ADDRESS_LENGTH,
Buffer,
CommandLineArgs,
convertBleAddressArrayToString,
DeviceConnectionState,
FIRMWARE_UPGRADE_METHODS,
HalvesInfo,
HOST_CONNECTION_COUNT_MAX,
isBitSet,
isEqualArray,
LeftSlotModules,
Expand Down Expand Up @@ -84,6 +84,8 @@ interface UsvDeviceConnectionState {
}

export const UHK_HID_DEVICE_NOT_CONNECTED = '[UhkHidDevice] Device is not connected';

const MAX_BLE_ADDRESSES_IN_NEW_PAIRING_RESPONSE = 10;
/**
* HID API wrapper to support unified logging and async write
*/
Expand Down Expand Up @@ -339,7 +341,7 @@ export class UhkHidDevice {
isMacroStatusDirty: false,
leftHalfDetected: false,
multiDevice: await getNumberOfConnectedDevices(this.options) > 1,
pairedDevices: [],
newPairedDevices: [],
udevRulesInfo: await this.getUdevInfoAsync(),
};

Expand Down Expand Up @@ -419,7 +421,7 @@ export class UhkHidDevice {
result.isMacroStatusDirty = deviceState.isMacroStatusDirty;

if (deviceState.newPairedDevice) {
result.pairedDevices = await this.getPairedDevices();
result.newPairedDevices = await this.getPairedDevices();
}
} else if (!result.connectedDevice) {
this._device = undefined;
Expand Down Expand Up @@ -681,13 +683,39 @@ export class UhkHidDevice {
}
}

async getPairedDevices(): Promise<number[]> {
this.logService.usb('[UhkHidDevice] USB[T]: Read paired devices');
const command = Buffer.from([UsbCommand.GetProperty, DevicePropertyIds.NewPairings]);
const buffer = await this.write(command);
const pairedDevices = convertBufferToIntArray(buffer);
async getPairedDevices(): Promise<string[]> {
this.logService.misc('[UhkHidDevice] Read paired devices');
let iteration = 0;
const result: string[] = [];

while (true) {
this.logService.usb('[UhkHidDevice] USB[T]: Read paired devices');
const command = Buffer.from([UsbCommand.GetProperty, DevicePropertyIds.NewPairings, iteration]);
const buffer = await this.write(command);
const uhkBuffer = UhkBuffer.fromArray(convertBufferToIntArray(buffer));
// skip the first byte
uhkBuffer.readUInt8();

const remainingNewConnections = uhkBuffer.readUInt8();

const count = Math.min(remainingNewConnections, MAX_BLE_ADDRESSES_IN_NEW_PAIRING_RESPONSE);

for (let i = 0; i < count; i++) {
const address = [];
for (let i = 0; i < BLE_ADDRESS_LENGTH; i++) {
address.push(uhkBuffer.readUInt8());
}
result.push(convertBleAddressArrayToString(address));
}

if (remainingNewConnections <= MAX_BLE_ADDRESSES_IN_NEW_PAIRING_RESPONSE) {
break;
}

return pairedDevices.slice(0, HOST_CONNECTION_COUNT_MAX + 1);
iteration += 1;
}

return result;
}

async getProtocolVersions(): Promise<ProtocolVersions> {
Expand Down
5 changes: 5 additions & 0 deletions packages/uhk-web/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
(updateApp)="updateApp()"
(doNotUpdateApp)="doNotUpdateApp()">
</app-update-available>
<ble-pairing-panel *ngIf="newPairedDevicesState?.showNewPairedDevicesPanel"
[@topNotificationPanelVisible]
[state]="newPairedDevicesState"
(addNewPairedDevices)="addPairedDevicesToHostConnections()"
/>
<dongle-pairing-panel *ngIf="donglePairingState?.showDonglePairingPanel"
[@topNotificationPanelVisible]
[state]="donglePairingState.state"
Expand Down
1 change: 1 addition & 0 deletions packages/uhk-web/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
}
}

ble-pairing-panel,
dongle-pairing-panel,
firmware-upgrade-panel,
app-update-available {
Expand Down
21 changes: 19 additions & 2 deletions packages/uhk-web/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EnableUsbStackTestAction, UpdateFirmwareAction } from './store/actions/
import {
AppState,
getDonglePairingState,
getNewPairedDevicesState,
getErrorPanelHeight,
getShowAppUpdateAvailable,
getParsedStatusBuffer,
Expand All @@ -23,10 +24,11 @@ import {
getShowFirmwareUpgradePanel
} from './store';
import { StartDonglePairingAction } from './store/actions/dongle-pairing.action';
import { AddNewPairedDevicesToHostConnectionsAction } from './store/actions/user-config';
import { ProgressButtonState } from './store/reducers/progress-button-state';
import { UpdateInfo } from './models/update-info';
import { ErrorPanelSizeChangedAction, KeyUpAction, KeyDownAction } from './store/actions/app';
import { DonglePairingState, OutOfSpaceWarningData } from './models';
import { BleAddingState, DonglePairingState, OutOfSpaceWarningData } from './models';
import { filter } from 'rxjs/operators';
import { SecondSideMenuContainerComponent } from './components/side-menu';

Expand Down Expand Up @@ -93,6 +95,7 @@ export class MainAppComponent implements OnDestroy {
@ViewChild(SecondSideMenuContainerComponent) secondarySideMenuContainer: SecondSideMenuContainerComponent;

donglePairingState: DonglePairingState;
newPairedDevicesState: BleAddingState;
showFirmwareUpgradePanel: boolean;
showUpdateAvailable: boolean;
updateInfo$: Observable<UpdateInfo>;
Expand All @@ -108,6 +111,7 @@ export class MainAppComponent implements OnDestroy {
};
statusBuffer: string;
private donglePairingStateSubscription: Subscription;
private newPairedDevicesStateSubscription: Subscription;
private errorPanelHeightSubscription: Subscription;
private keypressCapturing: boolean;
private saveToKeyboardStateSubscription: Subscription;
Expand All @@ -128,6 +132,11 @@ export class MainAppComponent implements OnDestroy {
this.donglePairingState = data;
this.cdRef.markForCheck();
});
this.newPairedDevicesStateSubscription = store.select(getNewPairedDevicesState)
.subscribe(data => {
this.newPairedDevicesState = data;
this.cdRef.markForCheck();
});
this.errorPanelHeightSubscription = store.select(getErrorPanelHeight)
.subscribe(height => {
this.splitSizes = {
Expand Down Expand Up @@ -192,6 +201,7 @@ export class MainAppComponent implements OnDestroy {

ngOnDestroy(): void {
this.donglePairingStateSubscription.unsubscribe();
this.newPairedDevicesStateSubscription.unsubscribe();
this.errorPanelHeightSubscription.unsubscribe();
this.saveToKeyboardStateSubscription.unsubscribe();
this.keypressCapturingSubscription.unsubscribe();
Expand Down Expand Up @@ -234,6 +244,10 @@ export class MainAppComponent implements OnDestroy {
this.store.dispatch(new KeyUpAction(event));
}

addPairedDevicesToHostConnections() {
this.store.dispatch(new AddNewPairedDevicesToHostConnectionsAction());
}

updateApp() {
this.store.dispatch(new UpdateAppAction());
}
Expand All @@ -251,7 +265,10 @@ export class MainAppComponent implements OnDestroy {
}

isTopNotificationPanelVisible(): boolean {
return this.showFirmwareUpgradePanel || this.showUpdateAvailable || this.donglePairingState?.showDonglePairingPanel;
return this.showFirmwareUpgradePanel
|| this.showUpdateAvailable
|| this.donglePairingState?.showDonglePairingPanel
|| this.newPairedDevicesState?.showNewPairedDevicesPanel;
}

updateFirmware(): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div class="ble-pairing-panel-wrapper" role="alert">
<span *ngIf="state.state === BlePairingStates.Idle">
<ng-container *ngIf="state.nrOfNewBleAddresses === 1">
A new Bluetooth device has been paired to this UHK. Let's <a routerLink="/device/host-connections" (click)="addNewPairedDevices.emit()">add it to connections</a> so that you can switch to it.
</ng-container>

<ng-container *ngIf="state.nrOfNewBleAddresses > 1">
New Bluetooth devices have been paired to this UHK. Let's <a routerLink="/device/host-connections" (click)="addNewPairedDevices.emit()">add them to connections</a> so that you can switch to them.
</ng-container>
</span>

<span *ngIf="state.state === BlePairingStates.Adding || state.state === BlePairingStates.SavingToKeyboard">
Bluetooth device(s) adding... <fa-icon [icon]="faSpinner" animation="spin"></fa-icon>
</span>

<span *ngIf="state.state === BlePairingStates.AddingSuccess">
Bluetooth device(s) added.
</span>

<span *ngIf="state.state === BlePairingStates.TooMuchHostConnections">
Can't save the configuration because there are too many connections. You must delete {{ state.nrOfHostConnections - hostConnectionsMaxCount }} connections.
</span>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
@import '../../../../styles/variables';

.ble-pairing-panel-wrapper {
display: flex;
justify-content: center;
align-items: center;
height: $main-content-top-margin-on-update;
margin: 0;
padding-top: 5px;
padding-bottom: 5px;

background-color: var(--color-firmware-upgrade-panel-bg);
color: var(--color-firmware-upgrade-panel-text);
border-radius: 0;
border-width: 0;

a {
color: var(--color-firmware-upgrade-panel-text);
text-decoration: underline;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { HOST_CONNECTION_COUNT_MAX } from 'uhk-common';

import { BleAddingState, BleAddingStates } from '../../../models';

@Component({
selector: 'ble-pairing-panel',
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './ble-pairing-panel.component.html',
styleUrls: ['./ble-pairing-panel.component.scss']
})
export class BlePairingPanelComponent {
@Input() state: BleAddingState;

@Output() addNewPairedDevices = new EventEmitter<void>();

protected readonly BlePairingStates = BleAddingStates;
protected readonly faSpinner = faSpinner;
protected readonly hostConnectionsMaxCount = HOST_CONNECTION_COUNT_MAX;
}
14 changes: 14 additions & 0 deletions packages/uhk-web/src/app/models/ble-adding-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export enum BleAddingStates {
Idle = 'Idle',
Adding = 'Adding',
AddingSuccess = 'AddingSuccess',
SavingToKeyboard = 'SavingToKeyboard',
TooMuchHostConnections = 'TooMuchHostConnections',
}

export interface BleAddingState {
showNewPairedDevicesPanel: boolean;
nrOfNewBleAddresses: number;
nrOfHostConnections: number;
state: BleAddingStates;
}
1 change: 1 addition & 0 deletions packages/uhk-web/src/app/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './apply-user-configuration-from-file-payload';
export * from './backlighting-option';
export * from './ble-adding-state';
export * from './config-size-state';
export * from './delete-host-connection-payload';
export * from './device-ui-states';
Expand Down
2 changes: 2 additions & 0 deletions packages/uhk-web/src/app/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
import { AddOnComponent } from './components/add-on';
import { BackToComponent } from './components/back-to/back-to.component';
import CircleTooltipComponent from './components/circle-tooltip/circle-tooltip.component';
import { BlePairingPanelComponent } from './components/device/ble-pairing-panel/ble-pairing-panel.component';
import { FadeTimeoutSliderComponent } from './components/device/led-settings/fade-timeout-slider.component';
import { DonglePairingPanelComponent } from './components/device/dongle-pairing-panel/dongle-pairing-panel.component';
import { KeyboardSliderComponent } from './components/keyboard/slider';
Expand Down Expand Up @@ -172,6 +173,7 @@ import appInitFactory from './services/app-init-factory';
AdvancedSettingsPageComponent,
AsHexColorPipe,
BackToComponent,
BlePairingPanelComponent,
HostConnectionTypeLabelPipePipe,
NewLineToBrPipe,
SafeHtmlPipe,
Expand Down
6 changes: 6 additions & 0 deletions packages/uhk-web/src/app/store/actions/user-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { NavigateToModuleSettingsPayload } from '../../models/navigate-to-module

export enum ActionTypes {
AddColorToBacklightingColorPalette = '[user-config] Add color to the backlighting color palette',
AddNewPairedDevicesToHostConnections = '[user-config] Add new paired devices to host connections',
DeleteColorFromBacklightingColorPalette = '[user-config] delete color from the backlighting color palette',
LoadUserConfig = '[user-config] Load User Config',
LoadConfigFromDevice = '[user-config] Load User Config from Device',
Expand Down Expand Up @@ -47,6 +48,10 @@ export class AddColorToBacklightingColorPaletteAction implements Action {
}
}

export class AddNewPairedDevicesToHostConnectionsAction implements Action {
type = ActionTypes.AddNewPairedDevicesToHostConnections;
}

export class DeleteColorFromBacklightingColorPaletteAction implements Action {
type = ActionTypes.DeleteColorFromBacklightingColorPalette;
}
Expand Down Expand Up @@ -203,6 +208,7 @@ export class RecoverLEDSpacesAction implements Action {

export type Actions
= AddColorToBacklightingColorPaletteAction
| AddNewPairedDevicesToHostConnectionsAction
| DeleteColorFromBacklightingColorPaletteAction
| LoadUserConfigAction
| LoadUserConfigSuccessAction
Expand Down
5 changes: 5 additions & 0 deletions packages/uhk-web/src/app/store/effects/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
FirmwareUpgradeIpcResponse,
getHardwareConfigFromDeviceResponse,
HardwareConfiguration,
HOST_CONNECTION_COUNT_MAX,
IpcResponse,
NotificationType,
shouldUpgradeAgent,
Expand Down Expand Up @@ -224,6 +225,10 @@ export class DeviceEffects {
if (shouldUpgradeFirmware)
return this.router.navigate(['/update-firmware']);

if (state.userConfiguration.userConfiguration.hostConnections.length > HOST_CONNECTION_COUNT_MAX) {
return;
}

setTimeout(() => this.sendUserConfigToKeyboard(
state.userConfiguration.userConfiguration,
state.app.hardwareConfig,
Expand Down
8 changes: 8 additions & 0 deletions packages/uhk-web/src/app/store/effects/user-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from 'uhk-common';

import { EmptyAction } from '../actions/app';
import { SaveConfigurationAction } from '../actions/device';
import {
ActionTypes,
ApplyUserConfigurationFromFileAction,
Expand Down Expand Up @@ -71,6 +72,12 @@ export class UserConfigEffects {
)
);

addNewPairedDevives$ = createEffect(() => this.actions$
.pipe(
ofType(ActionTypes.AddNewPairedDevicesToHostConnections),
map(() => new SaveConfigurationAction(true))
));

saveUserConfig$ = createEffect(() => this.actions$
.pipe(
ofType(
Expand All @@ -84,6 +91,7 @@ export class UserConfigEffects {
ActionTypes.RenameUserConfiguration, ActionTypes.SetUserConfigurationValue, ActionTypes.SetUserConfigurationRgbValue,
ActionTypes.RecoverLEDSpaces, ActionTypes.SetModuleConfigurationValue,
ActionTypes.ReorderHostConnections, ActionTypes.RenameHostConnection, ActionTypes.SetHostConnectionSwitchover,
ActionTypes.AddNewPairedDevicesToHostConnections,
),
withLatestFrom(this.store.select(getUserConfiguration), this.store.select(getPrevUserConfiguration)),
mergeMap(([action, config, prevUserConfiguration]) => {
Expand Down
1 change: 1 addition & 0 deletions packages/uhk-web/src/app/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export const isI2cDebuggingRingBellEnabled = createSelector(advanceSettingsState
export const userConfigState = (state: AppState) => state.userConfiguration;
export const getRouterState = (state: AppState) => state.router;

export const getNewPairedDevicesState = createSelector(userConfigState, fromUserConfig.getNewPairedDevicesState);
export const getUserConfiguration = createSelector(userConfigState, fromUserConfig.getUserConfiguration);
export const getKeymaps = createSelector(userConfigState, fromUserConfig.getKeymaps);
export const getHostConnections = createSelector(userConfigState, fromUserConfig.getHostConnections);
Expand Down
Loading

0 comments on commit 0183735

Please sign in to comment.