arkhipenko / TaskScheduler

Cooperative multitasking for Arduino, ESPx, STM32, nRF and other microcontrollers
http://playground.arduino.cc/Code/TaskScheduler
BSD 3-Clause "New" or "Revised" License
1.24k stars 226 forks source link

Keep trying if 'onEnable' fails #168

Closed bakadave closed 1 year ago

bakadave commented 1 year ago

I have implemented an MQTT client, when the task is enabled the onEnable method connects to the broker and the callback method handles the rest. My problem is that if the connection is ever broken this will only try to reconnect once and then the task is disabled forever. Ideally it would try to run the onEnable method every 'x' seconds until it reconnects. What would be the best way to go about it in your opinion?

Task t_mqtt(TASK_IMMEDIATE, TASK_FOREVER, &mqtt_callback, &ts, false, &mqtt_connect);

bool mqtt_connect() {
    return MQTTreconnect();
}

void mqtt_callback() {
    if (!mqtt.connected())
            t_mqtt.enable();    //run onEnable() method immediately and run this task afterwards
    mqtt.loop();
}

P.S.: I just realised I could utilise the onDisable method and maybe create a new task that re-enables t_mqtt after 'x' seconds. Still I'd prefer to do it without an extra task if possible.

bakadave commented 1 year ago

On second thought the problem with the onDisable approach is that if the onEnable method fails on the first try, onDisable is never called (at least according to the API documentation).

arkhipenko commented 1 year ago

Hi @bakadave I don't think onEnableshould be used this way. Also, you can run your mqtt_callback on an interval, not immediately to free up cycles for other tasks. So at every iteration, you check if you are still connected, and if not, reconnect. If you are - you service the MQTT client.

Why not this:

Task t_mqtt(10 * TASK_MILLISECOND, TASK_FOREVER, &mqtt_callback, &ts, false);

void mqtt_callback() {
    if (!mqtt.connected()) {
            MQTTreconnect();
            t_mqtt.delay(500);   // or however much time you want to give mqtt client to reconnect in the background
            return;
    }
    mqtt.loop();
}
bakadave commented 1 year ago

Hi @arkhipenko thank you for your feedback I understand this was not the way you intended for it to be used however I've come up with a method that is giving me satisfactory results. In this example I'll demostrate a roboust ethernet connection that handles disconnections and reconnections:

Task t_ethWD(1 * TASK_SECOND, TASK_FOREVER, &eth_watchdog, &ts, true);
Task t_eth(500 * TASK_MILLISECOND, TASK_FOREVER, &eth_callback, &ts, false, &eth_onEnable, &eth_onDisable);

/**
 * @brief callback() method of the main Ethernet task
 * If Ethernet connection is lost, it disables t_eth task which calls the onDisable() method and starts
 * the watchdog.
*/
void eth_callback() {
    if(Ethernet.linkStatus() == LinkOFF) {
        t_eth.disable();
        return;
    }

    int res = Ethernet.maintain();
    if(res == DHCP_REBIND_FAILED || res == DHCP_RENEW_FAILED)
        t_eth.disable();
}

/**
 * @brief onEnable() method of the main Ethernet task
 * If connection is successful it disables the Ethernet watchdog t_ethWD task.
*/
bool eth_onEnable() {
    if(Ethernet.linkStatus() != LinkON)
        return false;

    ts.pause();     //pause TaskScheduler before blocking call
    bool res = Ethernet.begin(mac);
    ts.resume();    //resume TaskScheduler

    if(res) {
        t_ethWD.disable();
        //start dependent tasks here
        return true;
    }
    return false;
}

/**
 * @brief onDisable() method of the main Ethernet task
 * It re-enables the Ethernet watchdog t_ethWD task.
*/
void eth_onDisable() {
    t_ethWD.enable();
    //stop dependent tasks here
}

/**
 * @brief callback() method of the Ethernet watchdog task
 * Enables the main Ethernet task on startup or after connection is lost.
 * Keeps trying to enable the main task every 5s until connection is reestabilished.
*/
void eth_watchdog() {
    t_eth.enable();
}

Ethernet watchdog t_ethWD is started after the Arduino setup() function is done. The watchdog tries to enable the main ethernet task t_eth every second. If the ethernet onEnable() method returns true the watchdog is disabled and the ethernet callback maintains DHCP every 500ms. If at any point ethernet connection is lost the main ethernet task is disabled and the onDisable() method starts the watchdog. This can be used to create a "task chain" if ethernet dependent tasks (like MQTT) are started when ethernet onEnable() returns true and stopped from the ethernet onDisable() method.

One thing this solution allows me to do is to not waste time connecting to MQTT when the ethernet is offline, also Ethernet.begin() has a really long timeout and it caused other tasks to "pile up" and be called multiple times after the timeout expired.

arkhipenko commented 1 year ago

I am glad you found a solution. By the way, in

    ts.pause();     //pause TaskScheduler before blocking call
    bool res = Ethernet.begin(mac);
    ts.resume();    //resume TaskScheduler

pause/resume are unnecessary IMHO. You are running in the same thread, so no task will run until you return anyways. pause/resume is good when you return to the scheduler.

bakadave commented 1 year ago

pause/resume are unnecessary IMHO. You are running in the same thread, so no task will run until you return anyways. pause/resume is good when you return to the scheduler.

Ethernet.begin(); can take multiple times longer than the period of many concurrently running tasks so if it runs all the way to timeout there were periodic tasks being called 2-3 times immediately after the thread finished.

arkhipenko commented 1 year ago

Got it! You can prevent it with scheduling options:

TASK_SCHEDULE - schedule is a priority, with "catch up" (default) TASK_SCHEDULE_NC - schedule is a priority, without "catch up" TASK_INTERVAL - interval is a priority, without "catch up"

https://github.com/arkhipenko/TaskScheduler/blob/master/examples/Scheduler_example26_SCHEDULING_OPTIONS/Scheduler_example26_SCHEDULING_OPTIONS.ino

arkhipenko commented 1 year ago

https://github.com/arkhipenko/TaskScheduler/issues/103

bakadave commented 1 year ago

Thank you will check it out.