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:
David Lechner
2022-12-21 12:40:47 -06:00
parent a12ae333f6
commit 23db8f4d9b
3 changed files with 32 additions and 2 deletions
+15
View File
@@ -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
View File
@@ -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)),