diff --git a/AGENTS.md b/AGENTS.md index 8e7640a..866cf5d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,6 +46,14 @@ automatically. That's packaging, not a fork. - Upstream moved the `v0.2.0` protocol tag (from `9c21d6e` to `1c008bc`) after tinclib v0.2.0 shipped. The files are identical; only the history was rewritten. +- **v0.4.1 (branch `win-serial-patch`): PC host fix.** With the calculator + plugged straight into a PC, `tinc_init()` gave up after 600 ms, before + Windows' usbser was loaded and the PC app had the port open. + `tinc_init()` now retries HELLO on `NO_REPLY` until `TINC_DEVICE_WAIT_MS`, + and a repeat `tinc_init()` keeps USB up instead of re-enumerating (which + made COM9 vanish on every retry). While the calculator is idle nothing + answers USB, so the PC's open blocks until the next tinclib call. Whether + 2 s is enough on a first plug-in is not yet measured. - **Real firmware, from a PC:** `make pc-link` builds `tools/pc_link.c` (the real `src/`, with srldrvce swapped for Win32 serial) and runs it against a board on a COM port. Against the v0.1 firmware on COM5, HELLO, diff --git a/src/tinc_core.c b/src/tinc_core.c index e1a0595..5b97a5a 100644 --- a/src/tinc_core.c +++ b/src/tinc_core.c @@ -219,25 +219,34 @@ tinc_err_t tinc_init(const tinc_config_t *cfg) tinc_err_t setup, err; clock_t start; - tinc_shutdown(); + if (tinc_g.ready) + tinc_abort(); memset(&tinc_g, 0, sizeof tinc_g); tinc_g.cfg = cfg ? *cfg : defaults; tinc_parser_init(&tinc_g.parser, tinc_g.frame, sizeof tinc_g.frame); setup = tinc_takeSetupResult(); - if (usb_Init(on_usb, NULL, srl_GetCDCStandardDescriptors(), USB_DEFAULT_INIT_FLAGS) - != USB_SUCCESS) { - usb_Cleanup(); - return setup ? setup : TINC_ERR_NO_DEVICE; + /* A retry keeps USB up: tearing it down makes a PC host re-enumerate + * the calculator, and its COM port goes away with it. */ + if (!usb_up) { + if (usb_Init(on_usb, NULL, srl_GetCDCStandardDescriptors(), + USB_DEFAULT_INIT_FLAGS) != USB_SUCCESS) { + usb_Cleanup(); + return setup ? setup : TINC_ERR_NO_DEVICE; + } + usb_up = true; } - usb_up = true; + /* A PC host is "connected" as soon as it configures us, but its app + * can only open the port while we pump USB events, so keep saying + * HELLO until the deadline. */ start = clock(); - while (!has_srl && !tinc_elapsed(start, TINC_DEVICE_WAIT_MS)) - usb_HandleEvents(); - - err = has_srl ? hello() : TINC_ERR_NO_DEVICE; + do { + while (!has_srl && !tinc_elapsed(start, TINC_DEVICE_WAIT_MS)) + usb_HandleEvents(); + err = has_srl ? hello() : TINC_ERR_NO_DEVICE; + } while (err == TINC_ERR_NO_REPLY && !tinc_elapsed(start, TINC_DEVICE_WAIT_MS)); tinc_g.ready = err == TINC_OK; return setup ? setup : err; } diff --git a/src/tinclib.h b/src/tinclib.h index fcde907..3b97a59 100644 --- a/src/tinclib.h +++ b/src/tinclib.h @@ -41,7 +41,7 @@ extern "C" { * X.Y, and the board's firmware must too. PATCH counts library-only releases * on the same protocol; reset it to 0 when the protocol submodule moves. */ -#define TINC_VERSION_PATCH 0 +#define TINC_VERSION_PATCH 1 #define TINC_STR_(x) #x #define TINC_STR(x) TINC_STR_(x) @@ -57,7 +57,10 @@ extern "C" { #define TINC_RX_BUF_SIZE 256 #endif -/** How long tinc_init() waits for the ESP board to show up on USB. */ +/** + * How long tinc_init() waits for the board (or a PC host) to show up on USB + * and answer HELLO. + */ #ifndef TINC_DEVICE_WAIT_MS #define TINC_DEVICE_WAIT_MS 2000u #endif diff --git a/tests/test_tinclib.c b/tests/test_tinclib.c index b36d65b..728c014 100644 --- a/tests/test_tinclib.c +++ b/tests/test_tinclib.c @@ -331,6 +331,26 @@ static void test_lost_reply_is_not_rerun(void) CHECK(strcmp(body, "0123456789") == 0); } +/* A PC host answers only once its app has the port open: HELLO keeps + * being retried until TINC_DEVICE_WAIT_MS, then gives up. */ +static void test_hello_late_reply(void) +{ + unsigned long start, elapsed_ms; + + setup(); + fake.drop_replies = TINC_RETRY_MAX * 2; + CHECK(tinc_init(NULL) == TINC_OK); + CHECK(fake.frames_in == TINC_RETRY_MAX * 2 + 1); + + setup(); + fake.drop_replies = 255; + start = fake_now; + CHECK(tinc_init(NULL) == TINC_ERR_NO_REPLY); + elapsed_ms = (fake_now - start) / 1000; + CHECK(elapsed_ms >= TINC_DEVICE_WAIT_MS); + CHECK(elapsed_ms < TINC_DEVICE_WAIT_MS + TINC_RETRY_MAX * TINC_REPLY_TIMEOUT_MS + 100); +} + static void test_board_gone_quiet(void) { tinc_request_t req = get("http://x/"); @@ -561,6 +581,7 @@ int main(void) RUN(test_last_chunk_held_until_read); RUN(test_is_active_mid_chunk); RUN(test_lost_reply_is_not_rerun); + RUN(test_hello_late_reply); RUN(test_board_gone_quiet); RUN(test_esp_reset_mid_request); RUN(test_request_errors);