mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-15 11:04:49 +00:00
hub: move hub info reducers to hub
Most of the hub state doesn't depend on the connection type. To make it generic, move it out of ble and into hub so that we can share it with usb.
This commit is contained in:
@@ -2,14 +2,6 @@
|
||||
// Copyright (c) 2021-2025 The Pybricks Authors
|
||||
|
||||
import { AnyAction } from 'redux';
|
||||
import {
|
||||
bleDIServiceDidReceiveFirmwareRevision,
|
||||
bleDIServiceDidReceivePnPId,
|
||||
} from '../ble-device-info-service/actions';
|
||||
import { PnpIdVendorIdSource } from '../ble-device-info-service/protocol';
|
||||
import { HubType, LegoCompanyId } from '../ble-lwp3-service/protocol';
|
||||
import { didReceiveStatusReport } from '../ble-pybricks-service/actions';
|
||||
import { Status, statusToFlag } from '../ble-pybricks-service/protocol';
|
||||
import {
|
||||
bleConnectPybricks,
|
||||
bleDidConnectPybricks,
|
||||
@@ -26,11 +18,6 @@ test('initial state', () => {
|
||||
expect(reducers(undefined, {} as AnyAction)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"connection": "ble.connection.state.disconnected",
|
||||
"deviceBatteryCharging": false,
|
||||
"deviceFirmwareVersion": "",
|
||||
"deviceLowBatteryWarning": false,
|
||||
"deviceName": "",
|
||||
"deviceType": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
@@ -73,87 +60,3 @@ test('connection', () => {
|
||||
).connection,
|
||||
).toBe(BleConnectionState.Connected);
|
||||
});
|
||||
|
||||
test('deviceName', () => {
|
||||
const testId = 'test-id';
|
||||
const testName = 'Test Name';
|
||||
|
||||
expect(
|
||||
reducers({ deviceName: '' } as State, bleDidConnectPybricks(testId, testName))
|
||||
.deviceName,
|
||||
).toBe(testName);
|
||||
|
||||
expect(
|
||||
reducers({ deviceName: testName } as State, bleDidDisconnectPybricks())
|
||||
.deviceName,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceType', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceType: '' } as State,
|
||||
bleDIServiceDidReceivePnPId({
|
||||
vendorIdSource: PnpIdVendorIdSource.BluetoothSig,
|
||||
vendorId: LegoCompanyId,
|
||||
productId: HubType.MoveHub,
|
||||
productVersion: 0,
|
||||
}),
|
||||
).deviceType,
|
||||
).toBe('Move hub');
|
||||
|
||||
expect(
|
||||
reducers({ deviceType: 'Move hub' } as State, bleDidDisconnectPybricks())
|
||||
.deviceType,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceFirmwareVersion', () => {
|
||||
const testVersion = '3.0.0';
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceFirmwareVersion: '' } as State,
|
||||
bleDIServiceDidReceiveFirmwareRevision(testVersion),
|
||||
).deviceFirmwareVersion,
|
||||
).toBe(testVersion);
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceFirmwareVersion: testVersion } as State,
|
||||
bleDidDisconnectPybricks(),
|
||||
).deviceFirmwareVersion,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceLowBatteryWarning', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceLowBatteryWarning: false } as State,
|
||||
didReceiveStatusReport(statusToFlag(Status.BatteryLowVoltageWarning), 0, 0),
|
||||
).deviceLowBatteryWarning,
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceLowBatteryWarning: true } as State,
|
||||
didReceiveStatusReport(
|
||||
~statusToFlag(Status.BatteryLowVoltageWarning),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
).deviceLowBatteryWarning,
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
reducers({ deviceLowBatteryWarning: true } as State, bleDidDisconnectPybricks())
|
||||
.deviceLowBatteryWarning,
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('deviceBatteryCharging', () => {
|
||||
expect(
|
||||
reducers({ deviceBatteryCharging: true } as State, bleDidDisconnectPybricks())
|
||||
.deviceBatteryCharging,
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
+1
-73
@@ -1,17 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2020-2022 The Pybricks Authors
|
||||
// Copyright (c) 2020-2025 The Pybricks Authors
|
||||
//
|
||||
// Manages state for the Bluetooth Low Energy connection.
|
||||
// This assumes that there is only one global connection to a single device.
|
||||
|
||||
import { Reducer, combineReducers } from 'redux';
|
||||
import {
|
||||
bleDIServiceDidReceiveFirmwareRevision,
|
||||
bleDIServiceDidReceivePnPId,
|
||||
} from '../ble-device-info-service/actions';
|
||||
import { getHubTypeName } from '../ble-device-info-service/protocol';
|
||||
import { didReceiveStatusReport } from '../ble-pybricks-service/actions';
|
||||
import { Status, statusToFlag } from '../ble-pybricks-service/protocol';
|
||||
import {
|
||||
bleConnectPybricks,
|
||||
bleDidConnectPybricks,
|
||||
@@ -72,71 +65,6 @@ const connection: Reducer<BleConnectionState> = (
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceName: Reducer<string> = (state = '', action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (bleDidConnectPybricks.matches(action)) {
|
||||
return action.name;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceType: Reducer<string> = (state = '', action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (bleDIServiceDidReceivePnPId.matches(action)) {
|
||||
return getHubTypeName(action.pnpId);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceFirmwareVersion: Reducer<string> = (state = '', action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (bleDIServiceDidReceiveFirmwareRevision.matches(action)) {
|
||||
return action.version;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceLowBatteryWarning: Reducer<boolean> = (state = false, action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (didReceiveStatusReport.matches(action)) {
|
||||
return Boolean(
|
||||
action.statusFlags & statusToFlag(Status.BatteryLowVoltageWarning),
|
||||
);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceBatteryCharging: Reducer<boolean> = (state = false, action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: hub does not currently have a status flag for this
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
export default combineReducers({
|
||||
connection,
|
||||
deviceName,
|
||||
deviceType,
|
||||
deviceFirmwareVersion,
|
||||
deviceLowBatteryWarning,
|
||||
deviceBatteryCharging,
|
||||
});
|
||||
|
||||
@@ -7,9 +7,13 @@ import {
|
||||
bleDidDisconnectPybricks,
|
||||
bleDisconnectPybricks,
|
||||
} from '../ble/actions';
|
||||
import { bleDIServiceDidReceiveSoftwareRevision } from '../ble-device-info-service/actions';
|
||||
import { PnpId } from '../ble-device-info-service/protocol';
|
||||
import { HubType } from '../ble-lwp3-service/protocol';
|
||||
import {
|
||||
bleDIServiceDidReceiveFirmwareRevision,
|
||||
bleDIServiceDidReceivePnPId,
|
||||
bleDIServiceDidReceiveSoftwareRevision,
|
||||
} from '../ble-device-info-service/actions';
|
||||
import { PnpId, PnpIdVendorIdSource } from '../ble-device-info-service/protocol';
|
||||
import { HubType, LegoCompanyId } from '../ble-lwp3-service/protocol';
|
||||
import {
|
||||
blePybricksServiceDidNotReceiveHubCapabilities,
|
||||
blePybricksServiceDidReceiveHubCapabilities,
|
||||
@@ -40,6 +44,11 @@ type State = ReturnType<typeof reducers>;
|
||||
test('initial state', () => {
|
||||
expect(reducers(undefined, {} as AnyAction)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"deviceBatteryCharging": false,
|
||||
"deviceFirmwareVersion": "",
|
||||
"deviceLowBatteryWarning": false,
|
||||
"deviceName": "",
|
||||
"deviceType": "",
|
||||
"downloadProgress": null,
|
||||
"hasRepl": false,
|
||||
"maxBleWriteSize": 0,
|
||||
@@ -298,6 +307,90 @@ describe('runtime', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('deviceName', () => {
|
||||
const testId = 'test-id';
|
||||
const testName = 'Test Name';
|
||||
|
||||
expect(
|
||||
reducers({ deviceName: '' } as State, bleDidConnectPybricks(testId, testName))
|
||||
.deviceName,
|
||||
).toBe(testName);
|
||||
|
||||
expect(
|
||||
reducers({ deviceName: testName } as State, bleDidDisconnectPybricks())
|
||||
.deviceName,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceType', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceType: '' } as State,
|
||||
bleDIServiceDidReceivePnPId({
|
||||
vendorIdSource: PnpIdVendorIdSource.BluetoothSig,
|
||||
vendorId: LegoCompanyId,
|
||||
productId: HubType.MoveHub,
|
||||
productVersion: 0,
|
||||
}),
|
||||
).deviceType,
|
||||
).toBe('Move hub');
|
||||
|
||||
expect(
|
||||
reducers({ deviceType: 'Move hub' } as State, bleDidDisconnectPybricks())
|
||||
.deviceType,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceFirmwareVersion', () => {
|
||||
const testVersion = '3.0.0';
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceFirmwareVersion: '' } as State,
|
||||
bleDIServiceDidReceiveFirmwareRevision(testVersion),
|
||||
).deviceFirmwareVersion,
|
||||
).toBe(testVersion);
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceFirmwareVersion: testVersion } as State,
|
||||
bleDidDisconnectPybricks(),
|
||||
).deviceFirmwareVersion,
|
||||
).toBe('');
|
||||
});
|
||||
|
||||
test('deviceLowBatteryWarning', () => {
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceLowBatteryWarning: false } as State,
|
||||
didReceiveStatusReport(statusToFlag(Status.BatteryLowVoltageWarning), 0, 0),
|
||||
).deviceLowBatteryWarning,
|
||||
).toBeTruthy();
|
||||
|
||||
expect(
|
||||
reducers(
|
||||
{ deviceLowBatteryWarning: true } as State,
|
||||
didReceiveStatusReport(
|
||||
~statusToFlag(Status.BatteryLowVoltageWarning),
|
||||
0,
|
||||
0,
|
||||
),
|
||||
).deviceLowBatteryWarning,
|
||||
).toBeFalsy();
|
||||
|
||||
expect(
|
||||
reducers({ deviceLowBatteryWarning: true } as State, bleDidDisconnectPybricks())
|
||||
.deviceLowBatteryWarning,
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
test('deviceBatteryCharging', () => {
|
||||
expect(
|
||||
reducers({ deviceBatteryCharging: true } as State, bleDidDisconnectPybricks())
|
||||
.deviceBatteryCharging,
|
||||
).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('maxBleWriteSize', () => {
|
||||
test.each([100, 1000])('Pybricks Profile >= v1.2.0: %s', (size) => {
|
||||
expect(
|
||||
|
||||
+71
-1
@@ -8,7 +8,12 @@ import {
|
||||
bleDidDisconnectPybricks,
|
||||
bleDisconnectPybricks,
|
||||
} from '../ble/actions';
|
||||
import { bleDIServiceDidReceiveSoftwareRevision } from '../ble-device-info-service/actions';
|
||||
import {
|
||||
bleDIServiceDidReceiveFirmwareRevision,
|
||||
bleDIServiceDidReceivePnPId,
|
||||
bleDIServiceDidReceiveSoftwareRevision,
|
||||
} from '../ble-device-info-service/actions';
|
||||
import { getHubTypeName } from '../ble-device-info-service/protocol';
|
||||
import { HubType } from '../ble-lwp3-service/protocol';
|
||||
import {
|
||||
blePybricksServiceDidNotReceiveHubCapabilities,
|
||||
@@ -143,6 +148,66 @@ const runtime: Reducer<HubRuntimeState> = (
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceName: Reducer<string> = (state = '', action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (bleDidConnectPybricks.matches(action)) {
|
||||
return action.name;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceType: Reducer<string> = (state = '', action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (bleDIServiceDidReceivePnPId.matches(action)) {
|
||||
return getHubTypeName(action.pnpId);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceFirmwareVersion: Reducer<string> = (state = '', action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (bleDIServiceDidReceiveFirmwareRevision.matches(action)) {
|
||||
return action.version;
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceLowBatteryWarning: Reducer<boolean> = (state = false, action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (didReceiveStatusReport.matches(action)) {
|
||||
return Boolean(
|
||||
action.statusFlags & statusToFlag(Status.BatteryLowVoltageWarning),
|
||||
);
|
||||
}
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const deviceBatteryCharging: Reducer<boolean> = (state = false, action) => {
|
||||
if (bleDidDisconnectPybricks.matches(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: hub does not currently have a status flag for this
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
const downloadProgress: Reducer<number | null> = (state = null, action) => {
|
||||
if (didStartDownload.matches(action)) {
|
||||
return 0;
|
||||
@@ -299,6 +364,11 @@ const selectedSlot: Reducer<number> = (state = 0, action) => {
|
||||
|
||||
export default combineReducers({
|
||||
runtime,
|
||||
deviceName,
|
||||
deviceType,
|
||||
deviceFirmwareVersion,
|
||||
deviceLowBatteryWarning,
|
||||
deviceBatteryCharging,
|
||||
downloadProgress,
|
||||
maxBleWriteSize,
|
||||
maxUserProgramSize,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
import { act, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { testRender } from '../../test';
|
||||
import { BleConnectionState } from '../ble/reducers';
|
||||
import { HubRuntimeState } from '../hub/reducers';
|
||||
import StatusBar from './StatusBar';
|
||||
|
||||
@@ -12,15 +11,14 @@ it('should show popover when hub name is clicked', async () => {
|
||||
const testHubName = 'Test hub';
|
||||
|
||||
const [user, statusBar] = testRender(<StatusBar />, {
|
||||
ble: {
|
||||
connection: BleConnectionState.Connected,
|
||||
hub: {
|
||||
runtime: HubRuntimeState.Idle,
|
||||
deviceName: testHubName,
|
||||
deviceType: 'hub type',
|
||||
deviceFirmwareVersion: 'v0.0.0',
|
||||
deviceLowBatteryWarning: false,
|
||||
deviceBatteryCharging: false,
|
||||
},
|
||||
hub: { runtime: HubRuntimeState.Idle },
|
||||
});
|
||||
|
||||
await act(() => user.click(statusBar.getByText(testHubName)));
|
||||
@@ -32,15 +30,14 @@ it('should show popover when battery is clicked', async () => {
|
||||
const testHubName = 'Test hub';
|
||||
|
||||
const [user, statusBar] = testRender(<StatusBar />, {
|
||||
ble: {
|
||||
connection: BleConnectionState.Connected,
|
||||
hub: {
|
||||
runtime: HubRuntimeState.Idle,
|
||||
deviceName: testHubName,
|
||||
deviceType: 'hub type',
|
||||
deviceFirmwareVersion: 'v0.0.0',
|
||||
deviceLowBatteryWarning: false,
|
||||
deviceBatteryCharging: false,
|
||||
},
|
||||
hub: { runtime: HubRuntimeState.Idle },
|
||||
});
|
||||
|
||||
await act(() => user.click(statusBar.getByTitle('Battery')));
|
||||
|
||||
@@ -72,9 +72,9 @@ const CompletionEngineIndicator: React.FunctionComponent = () => {
|
||||
|
||||
const HubInfoButton: React.FunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
const deviceName = useSelector((s) => s.ble.deviceName);
|
||||
const deviceType = useSelector((s) => s.ble.deviceType);
|
||||
const deviceFirmwareVersion = useSelector((s) => s.ble.deviceFirmwareVersion);
|
||||
const deviceName = useSelector((s) => s.hub.deviceName);
|
||||
const deviceType = useSelector((s) => s.hub.deviceType);
|
||||
const deviceFirmwareVersion = useSelector((s) => s.hub.deviceFirmwareVersion);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
@@ -113,8 +113,8 @@ const HubInfoButton: React.FunctionComponent = () => {
|
||||
|
||||
const BatteryIndicator: React.FunctionComponent = () => {
|
||||
const i18n = useI18n();
|
||||
const charging = useSelector((s) => s.ble.deviceBatteryCharging);
|
||||
const lowBatteryWarning = useSelector((s) => s.ble.deviceLowBatteryWarning);
|
||||
const charging = useSelector((s) => s.hub.deviceBatteryCharging);
|
||||
const lowBatteryWarning = useSelector((s) => s.hub.deviceLowBatteryWarning);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
|
||||
Reference in New Issue
Block a user