mirror of
https://github.com/pybricks/pybricks-code.git
synced 2026-09-15 02:54:07 +00:00
terminal: fix input handler loop getting stuck
When no hub is connected, the terminal input handler loop would get stuck waiting for didWrite or didFailToWrite which would never come because handleWriteUart in ble/sagas only runs when a hub is connected. This is fixed by only dispatching a write action if a user program is running on the hub. By using this state, it also fixes characters being buffered before the user program starts, e.g. - connect hub - type into terminal - no echo - start the repl - previously typed characters are echoed after the repl prompt Now, anything typed before the user program is just ignored. Fixes: https://github.com/pybricks/support/issues/865 Also properly fixes https://github.com/pybricks/support/issues/303
This commit is contained in:
@@ -71,6 +71,7 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
|
||||
test('basic function works', async () => {
|
||||
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
|
||||
saga.updateState({ hub: { runtime: HubRuntimeState.Running } });
|
||||
|
||||
saga.put(receiveData('test1234'));
|
||||
|
||||
@@ -82,6 +83,7 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
|
||||
test('messages are queued until previous has completed', async () => {
|
||||
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
|
||||
saga.updateState({ hub: { runtime: HubRuntimeState.Running } });
|
||||
|
||||
saga.put(receiveData('test1234'));
|
||||
await delay(50); // without delay, messages are combined
|
||||
@@ -108,6 +110,7 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
|
||||
test('messages are queued until previous has failed', async () => {
|
||||
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
|
||||
saga.updateState({ hub: { runtime: HubRuntimeState.Running } });
|
||||
|
||||
saga.put(receiveData('test1234'));
|
||||
await delay(50); // without delay, messages are combined
|
||||
@@ -134,6 +137,7 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
|
||||
test('small messages are combined', async () => {
|
||||
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
|
||||
saga.updateState({ hub: { runtime: HubRuntimeState.Running } });
|
||||
|
||||
saga.put(receiveData('test1234'));
|
||||
saga.put(receiveData('test1234'));
|
||||
@@ -148,6 +152,7 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
const testData = '012345678901234567890123456789';
|
||||
|
||||
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
|
||||
saga.updateState({ hub: { runtime: HubRuntimeState.Running } });
|
||||
|
||||
saga.put(receiveData(testData));
|
||||
|
||||
@@ -163,4 +168,14 @@ describe('Terminal data source responds to receive data actions', () => {
|
||||
|
||||
await saga.end();
|
||||
});
|
||||
|
||||
test('if user program is not running, do not dispatch write', async () => {
|
||||
const saga = new AsyncSaga(terminal, { nextMessageId: createCountFunc() });
|
||||
saga.updateState({ hub: { runtime: HubRuntimeState.Disconnected } });
|
||||
|
||||
saga.put(receiveData('test1234'));
|
||||
|
||||
// line below will fail with unhandled pending dispatches if not working correctly
|
||||
await saga.end();
|
||||
});
|
||||
});
|
||||
|
||||
+15
-2
@@ -49,8 +49,10 @@ function* receiveUartData(action: ReturnType<typeof didNotify>): Generator {
|
||||
}
|
||||
|
||||
function* receiveTerminalData(): Generator {
|
||||
const nextMessageId = yield* getContext<() => number>('nextMessageId');
|
||||
const channel = yield* actionChannel(receiveData);
|
||||
while (true) {
|
||||
|
||||
for (;;) {
|
||||
// wait for input from terminal
|
||||
const action = yield* take(channel);
|
||||
let value = action.value;
|
||||
@@ -68,10 +70,21 @@ function* receiveTerminalData(): Generator {
|
||||
value += action.value;
|
||||
}
|
||||
|
||||
const nextMessageId = yield* getContext<() => number>('nextMessageId');
|
||||
const isUserProgramRunning = yield* select(
|
||||
(s: RootState) => s.hub.runtime === HubRuntimeState.Running,
|
||||
);
|
||||
|
||||
// REVISIT: this test is a bit dangerous since it is not actually
|
||||
// testing that handleWriteUart in ble/sagas is running. In theory
|
||||
// it should be fine as long a the logic for the state doesn't change.
|
||||
if (!isUserProgramRunning) {
|
||||
// if no user program is running, input goes to /dev/null
|
||||
continue;
|
||||
}
|
||||
|
||||
// stdin gets piped to BLE connection
|
||||
const data = encoder.encode(value);
|
||||
|
||||
for (let i = 0; i < data.length; i += nordicUartSafeTxCharLength) {
|
||||
const { id } = yield* put(
|
||||
write(nextMessageId(), data.slice(i, i + nordicUartSafeTxCharLength)),
|
||||
|
||||
Reference in New Issue
Block a user