/* main.c — the host robot: the whole stack with simulated hardware. * * This is the main loop from docs/flow.md §8.3, unmodified. Only the * transport and the HAL differ from the Pico build. */ #include "rubo/hal_sim.h" #include "rubo/rubo.h" #include #include #include rubo_transport_t *rubo_transport_stdio(void); static rubo_transport_t *g_tx; static uint32_t now_ms(void) { static struct timespec t0; struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); if (t0.tv_sec == 0) t0 = t; return (uint32_t)((t.tv_sec - t0.tv_sec) * 1000 + (t.tv_nsec - t0.tv_nsec) / 1000000); } static void sink(void *ctx, const char *line) { (void)ctx; printf("< %s\n", line); fflush(stdout); } int main(int argc, char **argv) { bool trace = false, script = false; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--trace") == 0) trace = true; if (strcmp(argv[i], "--script") == 0) script = true; } rubo_hal_sim_set_trace(trace); rubo_t *r = rubo_create(rubo_hal_sim(), sink, NULL); if (!r) { fprintf(stderr, "out of memory\n"); return 1; } if (rubo_load_stdlib(r, rubo_stdlib) != RUBO_OK) { fprintf(stderr, "standard library failed to load\n"); rubo_destroy(r); return 1; } g_tx = rubo_transport_stdio(); if (g_tx->open(g_tx) < 0) { fprintf(stderr, "cannot open stdin\n"); rubo_destroy(r); return 1; } rubo_framer_t fr; rubo_framer_reset(&fr); rubo_note_traffic(r, now_ms()); printf("rubo host — try: PING | STAT | SQUARE side=400 | ABORT\n"); printf(" multi-line DEF works; end with a newline at depth 0\n"); fflush(stdout); bool input_done = false; for (;;) { uint32_t t = now_ms(); uint8_t buf[128]; /* --script is the minimal correct client (docs/flow.md §9): one * outstanding statement at a time. It stops reading while a program * runs, and keeps the link alive meanwhile — which is exactly what * a real client must do, since the deadman does not care that the * operator is merely being quiet. */ size_t want = sizeof buf; if (script && rubo_state(r) != RUBO_IDLE) { rubo_note_traffic(r, t); want = 0; } else if (script) { want = 1; } int n = (input_done || want == 0) ? 0 : g_tx->read(g_tx, buf, want); if (n < 0) { /* End of stdin. On a real transport a disconnect means the * §10 guard fires; here it just means a script ran out, so the * running program is allowed to finish. */ input_done = true; n = 0; } /* Do not exit with the motors still turning: a `dur=0` move is left * running deliberately, and the thing that ends it is the deadman * (docs/flow.md §10). Leaving early would hide that. */ bool engaged = rubo_hal_sim_state()->left != 0 || rubo_hal_sim_state()->right != 0; if (input_done && rubo_state(r) == RUBO_IDLE && !engaged) break; for (int i = 0; i < n; i++) { int st = rubo_framer_push(&fr, (char)buf[i], t); if (st == 1) { rubo_submit(r, fr.buf, t); } else if (st < 0) { rubo_err_t e = (rubo_err_t)(-st); printf("< ERR %d %s\n", (int)e, rubo_strerror(e)); fflush(stdout); } } int to = rubo_framer_timeout(&fr, t); if (to < 0) { rubo_err_t e = (rubo_err_t)(-to); printf("< ERR %d incomplete statement\n", (int)e); fflush(stdout); } rubo_tick(r, t); /* The Pico build spins; here, yield so the host is not pegged. */ struct timespec nap = { .tv_sec = 0, .tv_nsec = 2 * 1000 * 1000 }; nanosleep(&nap, NULL); } rubo_destroy(r); return 0; }