Skip to content

spawn_tasks.tc

spawn_tasks.tc — Dynamic FreeRTOS task spawn demo (ESP32 only)

Source on GitHub

// spawn_tasks.tc — Dynamic FreeRTOS task spawn demo (ESP32 only)
//
// Three worker patterns:
//   1. "Blink"        — one-shot delayed job, fires via console command
//   2. "Downloader"   — background HTTP GET with shared-global result report
//   3. "Heartbeat"    — long-running killable worker
//
// Commands (type in Tasmota console — prefix "TCT" + subcommand, no space):
//   TCTBLINK     — spawn Blink if not already running
//   TCTDOWNLOAD  — spawn Downloader
//   TCTSTART     — spawn Heartbeat
//   TCTSTOP      — kill Heartbeat
//   TCTSTATUS    — log which of the three are currently running

// Shared globals — spawned tasks see and mutate these
int  hb_ticks      = 0;
int  dl_state      = 0;  // 0=idle, 1=ok, -1=fail
char dl_body[1024];
char dl_url[] = "http://worldtimeapi.org/api/timezone/Europe/Berlin";

// ── Worker 1: short "blink" sequence (logs on/off to console) ─────────
void Blink() {
    for (int i = 0; i < 5; i++) {
        char buf[32];
        sprintfInt(buf, "blink ON  %d", i);
        addLog(buf); delay(300);
        sprintfInt(buf, "blink OFF %d", i);
        addLog(buf); delay(300);
    }
    addLog("Blink done");
}

// ── Worker 2: background HTTP fetch (needs larger stack) ──────────────
void Downloader() {
    addLog("Downloader start");
    int rc = httpGet(dl_url, dl_body);
    dl_state = (rc > 0) ? 1 : -1;
}

// ── Worker 3: endless killable heartbeat ──────────────────────────────
void Heartbeat() {
    hb_ticks = 0;
    while (1) {
        hb_ticks = hb_ticks + 1;
        char buf[48];
        sprintfInt(buf, "hb tick=%d", hb_ticks);
        addLog(buf);
        delay(2000);
    }
}

// ── Report HTTP result on main thread ─────────────────────────────────
void EverySecond() {
    if (dl_state == 1) {
        char buf[48];
        sprintfInt(buf, "download ok, %d bytes", strlen(dl_body));
        addLog(buf);
        dl_state = 0;
    } else if (dl_state == -1) {
        addLog("download failed");
        dl_state = 0;
    }
}

// ── Console command handler ───────────────────────────────────────────
// Note: spawnTask / killTask / taskRunning still require a string literal
// for the task-name arg (enforced at compile time).
// Each branch must call responseCmnd(...) so Tasmota emits a normal
// {"TCT..":"<msg>"} response instead of {"Command":"Error",...}.
void Command(char cmd[]) {
    char resp[96];
    if (strcmp(cmd, "BLINK") == 0) {
        if (taskRunning("Blink")) { responseCmnd("Blink busy"); return; }
        int r = spawnTask("Blink");
        sprintfInt(resp, "Blink spawned (rc=%d)", r);
        responseCmnd(resp);
    }
    else if (strcmp(cmd, "DOWNLOAD") == 0) {
        if (taskRunning("Downloader")) { responseCmnd("Downloader busy"); return; }
        // 6 KB stack — HTTP client needs more than default 3 KB
        int r = spawnTask("Downloader", 6);
        sprintfInt(resp, "Downloader spawned (rc=%d)", r);
        responseCmnd(resp);
    }
    else if (strcmp(cmd, "START") == 0) {
        if (taskRunning("Heartbeat")) { responseCmnd("Heartbeat busy"); return; }
        int r = spawnTask("Heartbeat");
        sprintfInt(resp, "Heartbeat spawned (rc=%d)", r);
        responseCmnd(resp);
    }
    else if (strcmp(cmd, "STOP") == 0) {
        int r = killTask("Heartbeat");
        if (r < 0) responseCmnd("Heartbeat not running");
        else       responseCmnd("Heartbeat stop signaled");
    }
    else if (strcmp(cmd, "STATUS") == 0) {
        sprintf(resp, "blink=%d dl=%d hb=%d",
                taskRunning("Blink"), taskRunning("Downloader"),
                taskRunning("Heartbeat"));
        responseCmnd(resp);
    }
    else {
        responseCmnd("unknown TCT subcommand");
    }
}

int main() {
    addCommand("TCT");
    addLog("spawn_tasks demo ready — try TCTBLINK / TCTDOWNLOAD / TCTSTART / TCTSTOP / TCTSTATUS");
    return 0;
}