Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WebSerial not getting enough time to write to SerialPort #21581

Open
koen1711 opened this issue Mar 21, 2024 · 6 comments
Open

WebSerial not getting enough time to write to SerialPort #21581

koen1711 opened this issue Mar 21, 2024 · 6 comments

Comments

@koen1711
Copy link

Version of emscripten/emsdk:
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 3.1.56 (cf90417)
clang version 19.0.0git (https:/github.com/llvm/llvm-project 34ba90745fa55777436a2429a51a3799c83c6d4c)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /home/koen/Documents/emsdk/upstream/bin

So I am using WebSerial in WebAssembly, when I try to write things like this my Arduino doesn't show any data recieved, but when I make the emscripten_sleep delay bigger, say 1000 it works fine. I suspect that JavaScript doesn't have enough time to process the write. How can I make sure JS does have that time?

EM_ASYNC_JS(void, write_data, (EM_VAL data), {
    data = new Uint8Array(Emval.toValue(data));
    console.log("Sending: ", data);
    const port = window.activePort;
    await window.writeStream.ready;
    await window.writeStream.write(data);
    await window.writeStream.ready;
});

void serialPortWrite(const unsigned char *buf, size_t len) {
    std::vector<unsigned char> data(buf, buf + len);
    write_data(val(typed_memory_view(data.size(), data.data())).as_handle());
    emscripten_sleep(0);
}
@sbc100
Copy link
Collaborator

sbc100 commented Mar 21, 2024

Can you add a console.log to the end of write_data function and just before the call the emscripten_sleep to confirm that write_data doesn't return to the caller until after the last of the 3 await statements?

I imagine what is happening here is that the webserial API requires you to return to event loop in order to do what i needs to do. emscripten_sleep is just one way to return to the event loop. You could also try using emscripten_set_main_loop.

BTW is this code running in on the main browser thread? If so I guess you have to be returning to the event loops anyway.

As always, could you share the full set of link flags you are using?

@koen1711
Copy link
Author

koen1711 commented Mar 21, 2024

Thank you for your reply!

First I'll give some more context, I am porting avrdude to webassembly, and yes it runs on the main browser thread, but I get the same results using pthread.

Here are my flags for with pthread:

-- CMAKE_CXX_FLAGS: -fPIC -s ALLOW_BLOCKING_ON_MAIN_THREAD -s PROXY_TO_PTHREAD -s EXPORT_NAME=avrdude -pthread -s ASYNCIFY_IMPORTS=['serialPortWrite','serialPortRecv'] -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s MAIN_MODULE -s WASM=1 -s FORCE_FILESYSTEM -s ASYNCIFY=1 -s INVOKE_RUN=0 -s WASM_BIGINT=1 -s MODULARIZE=1 -s EXPORT_KEEPALIVE=1 --bind -s "EXPORTED_FUNCTIONS=['_startAvrdude','_main','_malloc']" -s EXPORTED_RUNTIME_METHODS='["cwrap", "writeStringToMemory", "FS", "allocate", "intArrayFromString"]'

And without:

-- CMAKE_CXX_FLAGS: -fPIC -s ASYNCIFY_IMPORTS=['serialPortWrite','serialPortRecv'] -s ERROR_ON_UNDEFINED_SYMBOLS=0 -s MAIN_MODULE -s WASM=1 -s FORCE_FILESYSTEM -s ASYNCIFY=1 -s INVOKE_RUN=0 -s WASM_BIGINT=1 -s MODULARIZE=1 -s EXPORT_KEEPALIVE=1 --bind -s "EXPORTED_FUNCTIONS=['_startAvrdude','_main','_malloc']" -s EXPORTED_RUNTIME_METHODS='["cwrap", "writeStringToMemory", "FS", "allocate", "intArrayFromString"]'

My first thought was also that the write_data function was completed later or something but it is not sadly. But here is the updated code and respective output just to make sure.

EM_ASYNC_JS(void, write_data, (EM_VAL data), {
    data = new Uint8Array(Emval.toValue(data));
    console.log("Sending: ", data);
    const port = window.activePort;
    await window.writeStream.ready;
    await window.writeStream.write(data);
    await window.writeStream.ready;
    console.log("Data sent");
});

void serialPortWrite(const unsigned char *buf, size_t len) {
    std::vector<unsigned char> data(buf, buf + len);
    write_data(val(typed_memory_view(data.size(), data.data())).as_handle());
    printf("Data sent from C\n");
    emscripten_sleep(0);
}

Output:
Sending: Uint8Array(2) [48, 32, buffer: ArrayBuffer(2), byteLength: 2, byteOffset: 0, length: 2, Symbol(Symbol.toStringTag): 'Uint8Array'] avrdude.js:8 Data sent avrdude.js:8 Data sent from C

@sbc100
Copy link
Collaborator

sbc100 commented Mar 21, 2024

What is your program doing right after the serialPortWrite? Assuming its sitting the event loop then I don't see who emscripten_sleep can be helping since all it does is return to the event loop anyway.

@koen1711
Copy link
Author

It runs some more webassembly code it sends the 48,32 packet three times to my arduino. When it is done with those operations it will read the return data of the arduino.

EM_ASYNC_JS(void, read_data, (int timeoutMs), {
    const reader = window.activePort.readable.getReader();
    console.log("Reading data");
    async function receive() {
        const { value } = await reader.read();
        return value;
    }

    async function timeout(timeoutMs) {
        await new Promise(resolve => setTimeout(resolve, timeoutMs));
        return "timeout";
    }

    var returnBuffer = new Uint8Array();
    while (true) {
        let result = await Promise.race([receive(), timeout(timeoutMs)]);

        if (result instanceof Uint8Array) {
            // check if it is twice the same data so check if the first half is the same as the second half if so remove the second half
            let firstHalf = result.slice(0, result.length / 2);
            let secondHalf = result.slice(result.length / 2, result.length);
            if (firstHalf.every((value, index) => value === secondHalf[index])) {
                result = firstHalf;
            }

            console.log("Received: ", result);
            const ptr = window.funcs._malloc(result.length * Uint8Array.BYTES_PER_ELEMENT);
            window.funcs.HEAPU8.set(result, ptr);

            // Call the C++ function with the pointer and the length of the array
            window.funcs._dataCallback(ptr, result.length);
            break;
        } else {
            console.log("Timeout", result);
            break;
        }
    }
    reader.releaseLock();
    return;
});


int serialPortRecv(unsigned char *buf, size_t len, int timeoutMs) {
    std::vector<unsigned char> data = {};
    data.reserve(len);
    // check if there is leftover data from previous reads
    if (!readBuffer.empty()) {
        if (readBuffer.size() >= len) {
            data = std::vector<unsigned char>(readBuffer.begin(), readBuffer.begin() + len);
            readBuffer.erase(readBuffer.begin(), readBuffer.begin() + len);
            std::copy(data.begin(), data.end(), buf);
            return 0;
        } else {
            data = std::vector<unsigned char>(readBuffer.begin(), readBuffer.end());
            readBuffer.clear();
        }
    }
    read_data(timeoutMs);
    if (!readBuffer.empty()) {
        // check how much data is needed and add that much to the buffer
        if (readBuffer.size() >= len) {
            data = std::vector<unsigned char>(readBuffer.begin(), readBuffer.begin() + len);
            readBuffer.erase(readBuffer.begin(), readBuffer.begin() + len);
        } else {
            data = std::vector<unsigned char>(readBuffer.begin(), readBuffer.end());
            readBuffer.clear();
        }
    }
    if (data.empty()) {
        // fill data buf with 1s if no data was received
        printf("No data received\n");
        return -1;
    } else {
        std::copy(data.begin(), data.end(), buf);
    }
    return 0;
}

@koen1711
Copy link
Author

Some more testing reveals that the requirement is 300ms which works fine, but makes this code incredibly slow.

@koen1711
Copy link
Author

@sbc100

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants