mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-12 01:23:52 +00:00
+3
-5
@@ -21,15 +21,13 @@ module.exports = {
|
||||
'no-multi-spaces': 'error',
|
||||
'no-trailing-spaces': 'error',
|
||||
'no-multiple-empty-lines': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ["error", { argsIgnorePattern: "^_" }],
|
||||
'no-unused-vars': ["error", { argsIgnorePattern: "^_" }],
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'sort-imports': ['error', { ignoreDeclarationSort: true }],
|
||||
'import/order': ['error', { alphabetize: { order: 'asc' } }],
|
||||
},
|
||||
settings: {
|
||||
react: { version: 'detect' },
|
||||
},
|
||||
ignorePatterns: [
|
||||
"test/env.js"
|
||||
],
|
||||
ignorePatterns: ['test/env.js'],
|
||||
};
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pybricks/pybricks-code",
|
||||
"version": "1.0.0-beta.7",
|
||||
"version": "1.0.0-beta.8",
|
||||
"license": "MIT",
|
||||
"author": "The Pybricks Authors",
|
||||
"repository": {
|
||||
@@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.41.0",
|
||||
"@craco/craco": "^6.1.1",
|
||||
"@pybricks/firmware": "4.7.0",
|
||||
"@pybricks/firmware": "4.7.1",
|
||||
"@pybricks/ide-docs": "1.1.1",
|
||||
"@pybricks/mpy-cross-v5": "^2.0.0",
|
||||
"@shopify/react-i18n": "^5.3.0",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright (c) 2021 The Pybricks Authors
|
||||
//
|
||||
// Pybricks uses the standard Device Info service.
|
||||
// Refer to Device Information Service (DIS) at https://www.bluetooth.com/specifications/specs/
|
||||
// and assigned numbers at https://www.bluetooth.com/specifications/assigned-numbers/
|
||||
|
||||
/** Device Information service UUID. */
|
||||
export const serviceUUID = 0x180a;
|
||||
|
||||
/** Firmware Revision String characteristic UUID. */
|
||||
export const firmwareRevisionStringUUID = 0x2a26;
|
||||
|
||||
/** Software Revision String characteristic UUID. */
|
||||
export const softwareRevisionStringUUID = 0x2a28;
|
||||
+87
-3
@@ -15,6 +15,11 @@ import {
|
||||
takeEvery,
|
||||
takeMaybe,
|
||||
} from 'typed-redux-saga/macro';
|
||||
import {
|
||||
serviceUUID as deviceInfoServiceUUID,
|
||||
firmwareRevisionStringUUID,
|
||||
softwareRevisionStringUUID,
|
||||
} from '../ble-device-info-service/protocol';
|
||||
import {
|
||||
BlePybricksServiceActionType,
|
||||
didFailToWriteCommand,
|
||||
@@ -53,6 +58,8 @@ import {
|
||||
TxCharUUID as uartTxCharUUID,
|
||||
} from './protocol';
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
function disconnect(
|
||||
server: BluetoothRemoteGATTServer,
|
||||
_action: BleDeviceDisconnectAction,
|
||||
@@ -109,7 +116,11 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
device = yield* call(() =>
|
||||
navigator.bluetooth.requestDevice({
|
||||
filters: [{ services: [pybricksServiceUUID] }],
|
||||
optionalServices: [pybricksServiceUUID, uartServiceUUID],
|
||||
optionalServices: [
|
||||
pybricksServiceUUID,
|
||||
deviceInfoServiceUUID,
|
||||
uartServiceUUID,
|
||||
],
|
||||
}),
|
||||
);
|
||||
} catch (err) {
|
||||
@@ -145,6 +156,79 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
|
||||
yield* takeEvery(BLEDeviceActionType.Disconnect, disconnect, server);
|
||||
|
||||
let deviceInfoService: BluetoothRemoteGATTService;
|
||||
try {
|
||||
deviceInfoService = yield* call(
|
||||
[server, 'getPrimaryService'],
|
||||
deviceInfoServiceUUID,
|
||||
);
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
|
||||
yield* put(didFailToConnect({ reason: Reason.NoDeviceInfoService }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let firmwareVersionChar: BluetoothRemoteGATTCharacteristic;
|
||||
try {
|
||||
firmwareVersionChar = yield* call(
|
||||
[deviceInfoService, 'getCharacteristic'],
|
||||
firmwareRevisionStringUUID,
|
||||
);
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
return;
|
||||
}
|
||||
|
||||
let firmwareVersion: string;
|
||||
try {
|
||||
firmwareVersion = decoder.decode(
|
||||
yield* call([firmwareVersionChar, 'readValue']),
|
||||
);
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: save firmware version for later use
|
||||
console.log(`Hub firmware version: ${firmwareVersion}`);
|
||||
|
||||
let softwareVersionChar: BluetoothRemoteGATTCharacteristic;
|
||||
try {
|
||||
softwareVersionChar = yield* call(
|
||||
[deviceInfoService, 'getCharacteristic'],
|
||||
softwareRevisionStringUUID,
|
||||
);
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
return;
|
||||
}
|
||||
|
||||
let protocolVersion: string;
|
||||
try {
|
||||
protocolVersion = decoder.decode(
|
||||
yield* call([softwareVersionChar, 'readValue']),
|
||||
);
|
||||
} catch (err) {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: verify that minimum protocol version is met
|
||||
console.log(`Pybricks protocol version: ${protocolVersion}`);
|
||||
|
||||
let pybricksService: BluetoothRemoteGATTService;
|
||||
try {
|
||||
pybricksService = yield* call(
|
||||
@@ -155,7 +239,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
|
||||
yield* put(didFailToConnect({ reason: Reason.NoService }));
|
||||
yield* put(didFailToConnect({ reason: Reason.NoPybricksService }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
}
|
||||
@@ -231,7 +315,7 @@ function* connect(_action: BleDeviceConnectAction): Generator {
|
||||
server.disconnect();
|
||||
yield* takeMaybe(disconnectChannel);
|
||||
if (err instanceof DOMException && err.code === DOMException.NOT_FOUND_ERR) {
|
||||
yield* put(didFailToConnect({ reason: Reason.NoService }));
|
||||
yield* put(didFailToConnect({ reason: Reason.NoPybricksService }));
|
||||
} else {
|
||||
yield* put(didFailToConnect({ reason: Reason.Unknown, err }));
|
||||
}
|
||||
|
||||
+7
-3
@@ -58,7 +58,8 @@ export enum BleDeviceFailToConnectReasonType {
|
||||
NoBluetooth = 'ble.device.didFailToConnect.noBluetooth',
|
||||
Canceled = 'ble.device.didFailToConnect.canceled',
|
||||
NoGatt = 'ble.device.didFailToConnect.noGatt',
|
||||
NoService = 'ble.device.didFailToConnect.noService',
|
||||
NoDeviceInfoService = 'ble.device.didFailToConnect.noDeviceInfoService',
|
||||
NoPybricksService = 'ble.device.didFailToConnect.noPybricksService',
|
||||
Unknown = 'ble.device.didFailToConnect.unknown',
|
||||
}
|
||||
|
||||
@@ -74,7 +75,9 @@ export type BleDeviceFailToConnectCanceledReason = Reason<BleDeviceFailToConnect
|
||||
|
||||
export type BleDeviceFailToConnectNoGattReason = Reason<BleDeviceFailToConnectReasonType.NoGatt>;
|
||||
|
||||
export type BleDeviceFailToConnectNoServiceReason = Reason<BleDeviceFailToConnectReasonType.NoService>;
|
||||
export type BleDeviceFailToConnectNoDeviceInfoServiceReason = Reason<BleDeviceFailToConnectReasonType.NoDeviceInfoService>;
|
||||
|
||||
export type BleDeviceFailToConnectNoPybricksServiceReason = Reason<BleDeviceFailToConnectReasonType.NoPybricksService>;
|
||||
|
||||
export type BleDeviceFailToConnectUnknownReason = Reason<BleDeviceFailToConnectReasonType.Unknown> & {
|
||||
err: Error;
|
||||
@@ -85,7 +88,8 @@ export type BleDeviceDidFailToConnectReason =
|
||||
| BleDeviceFailToConnectNoBluetoothReason
|
||||
| BleDeviceFailToConnectCanceledReason
|
||||
| BleDeviceFailToConnectNoGattReason
|
||||
| BleDeviceFailToConnectNoServiceReason
|
||||
| BleDeviceFailToConnectNoDeviceInfoServiceReason
|
||||
| BleDeviceFailToConnectNoPybricksServiceReason
|
||||
| BleDeviceFailToConnectUnknownReason;
|
||||
|
||||
export type BleDeviceDidFailToConnectAction = Action<BleDeviceActionType.DidFailToConnect> &
|
||||
|
||||
@@ -37,6 +37,7 @@ body {
|
||||
|
||||
.#{$ns}-toast {
|
||||
user-select: text;
|
||||
max-width: 700px;
|
||||
}
|
||||
|
||||
.#{$ns}-button {
|
||||
|
||||
@@ -20,5 +20,20 @@ export default function Notification(props: OwnProps): JSX.Element {
|
||||
fallback: en,
|
||||
});
|
||||
const { messageId, replacements } = props;
|
||||
return <>{i18n.translate(messageId, replacements)}</>;
|
||||
let message = i18n.translate(messageId, replacements) as
|
||||
| React.ReactElement
|
||||
| string;
|
||||
|
||||
// Use newline characters to create paragraphs
|
||||
if (typeof message === 'string') {
|
||||
message = (
|
||||
<>
|
||||
{message.split('\n').map((x, i) => (
|
||||
<p key={i}>{x}</p>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
},
|
||||
"ble": {
|
||||
"gattPermission": "The web browser did not give permission to use Bluetooth Low Energy",
|
||||
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service. Try removing the \"{hubName}\" device in your OS Bluetooth settings, then try again.",
|
||||
"gattServiceNotFound": "Connected to hub but failed to get {serviceName} service.\nEnsure that you are using the most recent firmware.\nIf the problem persists, try removing the \"{hubName}\" device in your OS Bluetooth settings, then try connecting again.",
|
||||
"noWebBluetooth": "This web browser does not support Web Bluetooth or it is not enabled.",
|
||||
"noBluetooth": "No Bluetooth adapter could be found. Bluetooth won't work.",
|
||||
"unexpectedError": "Unexpected error while trying to connect: {errorMessage}"
|
||||
},
|
||||
"editor": {
|
||||
"programChanged": {
|
||||
"message": "The program was changed in another window. Do you want to delete this program and replace it with the new program?",
|
||||
"message": "The program was changed in another window.\nDo you want to delete this program and replace it with the new program?",
|
||||
"action": "Reload"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -31,7 +31,10 @@ test.each([
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoWebBluetooth }),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoBluetooth }),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoGatt }),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoService }),
|
||||
bleDidFailToConnect({
|
||||
reason: BleDeviceFailToConnectReasonType.NoDeviceInfoService,
|
||||
}),
|
||||
bleDidFailToConnect({ reason: BleDeviceFailToConnectReasonType.NoPybricksService }),
|
||||
bleDidFailToConnect({
|
||||
reason: BleDeviceFailToConnectReasonType.Unknown,
|
||||
err: { name: 'test', message: 'unknown' },
|
||||
|
||||
@@ -168,12 +168,18 @@ function* showBleDeviceDidFailToConnectError(
|
||||
yield* showSingleton(Level.Error, MessageId.BleGattPermission);
|
||||
break;
|
||||
|
||||
case BleDeviceFailToConnectReasonType.NoService:
|
||||
case BleDeviceFailToConnectReasonType.NoPybricksService:
|
||||
yield* showSingleton(Level.Error, MessageId.BleGattServiceNotFound, {
|
||||
serviceName: 'Pybricks',
|
||||
hubName: 'Pybricks Hub',
|
||||
});
|
||||
break;
|
||||
case BleDeviceFailToConnectReasonType.NoDeviceInfoService:
|
||||
yield* showSingleton(Level.Error, MessageId.BleGattServiceNotFound, {
|
||||
serviceName: 'Device Information',
|
||||
hubName: 'Pybricks Hub',
|
||||
});
|
||||
break;
|
||||
case BleDeviceFailToConnectReasonType.NoBluetooth:
|
||||
yield* showSingleton(Level.Error, MessageId.BleNoBluetooth);
|
||||
break;
|
||||
@@ -310,7 +316,11 @@ function* dismissCompilerError(): Generator {
|
||||
|
||||
function* showCompilerError(action: MpyDidFailToCompileAction): Generator {
|
||||
yield* showSingleton(Level.Error, MessageId.MpyError, {
|
||||
errorMessage: React.createElement('pre', undefined, action.err.join('\n')),
|
||||
errorMessage: React.createElement(
|
||||
'pre',
|
||||
{ style: { whiteSpace: 'pre-wrap', wordBreak: 'keep-all' } },
|
||||
action.err.join('\n'),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1500,10 +1500,10 @@
|
||||
schema-utils "^2.6.5"
|
||||
source-map "^0.7.3"
|
||||
|
||||
"@pybricks/firmware@4.7.0":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@pybricks/firmware/-/firmware-4.7.0.tgz#5b3cdfa009dc65ac7d3e3ccd769ab74143a1bda6"
|
||||
integrity sha512-Ln5puxxiEiWOv39AFja4Rr7OeAhxe/yJnWFI0IJXVnAPqFR9FIrME8rDZhPXyw5yIT76MMUuv6B21IJmA9WURw==
|
||||
"@pybricks/firmware@4.7.1":
|
||||
version "4.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@pybricks/firmware/-/firmware-4.7.1.tgz#2d800bfc9ce2096e6790a43393a5d0f8a795c94f"
|
||||
integrity sha512-nsYNhN2vOkclujsmeXyaziFz3xoCeLElUm81lQahxCCFl+bYaqQtXqhXqQ/pkYTWq+5bcd5cG8u/Y49jpeZ57A==
|
||||
dependencies:
|
||||
jszip "^3.5.0"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user