Taskscheduler in custom library

Hi,

i am want to use the TaskScheduler in my custom library but I have a problem to enter a library void into the constructor.

My library definition is:

TaskExample.h

#ifndef TaskExample_h
#define TaskExample_h

#include <TaskScheduler.h>

class TaskExample
{
    public:
    TaskExample();
    void defineMyTask();
    void taskToExecute();

    Task *myTask;
    Scheduler ts;

};

#endif

TaskExample.cpp

#include "TaskExample.h"


TaskExample::TaskExample(){}

void TaskExample::defineMyTask(){
    
    myTask = new Task (500,TASK_FOREVER, taskToExecute, &ts, false); // Error 
    myTask = new Task (500,TASK_FOREVER, this.taskToExecute, &ts, false); // Error 
    myTask = new Task (500,TASK_FOREVER, TaskExample::taskToExecute, &ts, false); // Error     
    myTask = new Task (500,TASK_FOREVER, externalVoid, &ts, false); // No error
}

void externalVoid(){
    Serial.println("i am executed");
}

void TaskExample::taskToExecute(){
    Serial.println("i am executed");
}

All lines in the void “defineMyTask” runs into an error except the last one. In the last line I add a not library void which works ok.

Can anybody help me to add a library void into the constructor?

I don’t have that library - are the functions you are trying to use private functions?

Please post your test sketch and the full error messages

All functions I want to use are public and works fine with not library voids (wee 4. line).

i tried 3 ways to add my library void but for each I got an error.

The errors are:

.pio\build\esp32dev\src\main.cpp.o:(.literal._Z5setupv+0x4): undefined reference to `TaskExample::defineMyTask()'
.pio\build\esp32dev\src\main.cpp.o:(.literal.startup._GLOBAL__sub_I__ZN4TaskC2EmlPFvvEP9SchedulerbPFbvES1_+0x4): undefined reference to `TaskExample::TaskExample()'
.pio\build\esp32dev\src\main.cpp.o: In function `setup()':
C:\Users\swagner5073\Documents\PlatformIO\Projects\Bonogps/src/main.cpp:8: undefined reference to `TaskExample::defineMyTask()'
.pio\build\esp32dev\src\main.cpp.o: In function `_GLOBAL__sub_I__ZN4TaskC2EmlPFvvEP9SchedulerbPFbvES1_':
C:\Users\swagner5073\Documents\PlatformIO\Projects\Bonogps/src/main.cpp:5: undefined reference to `TaskExample::TaskExample()'

The main.cpp is:

#include <Arduino.h>
#include <TaskExample.h>


TaskExample taskExample;

void setup(){
    taskExample.defineMyTask();    

}

void loop(){

}

Please, they are functions, not “voids”

Is TaskExample supposed to inherit from the library class?

Don´t know if it supposed to..

@r1snake,
From the error message, it looks like you're compiling for an ESP32. If so, why are you bothering with the TaskScheduler library? ESP32 runs FreeRTOS. Just use the task control features provided by built-in OS.

Do you have an example for me?

Or can you write me the code for my library void?

I can be wrong but try anyway

myTask = new Task (500,TASK_FOREVER, &TaskExample::taskToExecute, &ts, false);

ESP32 uses freeRTOS, no task scheduler needed.

Example:

declares:

#include "certs.h" // include the connection infor for WiFi and MQTT
#include "sdkconfig.h" // used for log printing
#include "esp_system.h"
#include "freertos/FreeRTOS.h" //freeRTOS items to be used
#include "freertos/task.h"
#include <driver/adc.h>
#include <SimpleKalmanFilter.h>

setup()

xTaskCreatePinnedToCore( fReadAD, "fReadAD", 9000, NULL, 3, NULL, 1 );

task

void fReadAD( void * parameter )
{
  float    ADbits = 4095.0f;
  float    uPvolts = 3.3f;
  float    adcValue_b = 0.0f; //plant in yellow pot
  uint64_t TimePastKalman  = esp_timer_get_time(); // used by the Kalman filter UpdateProcessNoise, time since last kalman calculation
  float    WetValue = 1.07f; // value found by putting sensor in water
  float    DryValue = 2.732f; // value of probe when held in air
  float    Range = DryValue - WetValue;
  float    RemainingMoisture = 100.0f;
  SimpleKalmanFilter KF_ADC_b( 1.0f, 1.0f, .01f );
  for (;;)
  {
    xEventGroupWaitBits (eg, evtADCreading, pdTRUE, pdTRUE, portMAX_DELAY ); //
    adcValue_b = float( adc1_get_raw(ADC1_CHANNEL_3) ); //take a raw ADC reading
    adcValue_b = ( adcValue_b * uPvolts ) / ADbits; //calculate voltage
    KF_ADC_b.setProcessNoise( (esp_timer_get_time() - TimePastKalman) / 1000000.0f ); //get time, in microsecods, since last readings
    adcValue_b = KF_ADC_b.updateEstimate( adcValue_b ); // apply simple Kalman filter
    TimePastKalman = esp_timer_get_time(); // time of update complete
    RemainingMoisture = 100.0f * (1 - ((adcValue_b - WetValue) / (DryValue - WetValue))); //remaining moisture =  1-(xTarget - xMin) / (xMax - xMin) as a percentage of the sensor wet dry volatges
    xQueueOverwrite( xQ_RM, (void *) &RemainingMoisture );
    //log_i( "adcValue_b = %f remaining moisture %f%", adcValue_b, RemainingMoisture );
  }
  vTaskDelete( NULL );
}

The above task is a thread that runs in the background when triggered.

Read the docs

More freeRTOS documentation

I find it a bit humorous running a task scheduler on an OS that is a task scheduler.

In parallel i read me in into the topic and created a test project.

This I my new library structure:

TaskExecute.h

#ifndef TaskExecute_h
#define TaskExecute_h

#include <Arduino.h>

class TaskExecute{
    public:

    TaskExecute();
    void defineMyTask();
    void taskToExecute();
    void taskOne(void * parameter);
    void taskTwo(void * parameter);

};


#endif

TaskExecute.cpp

#include <TaskExecute.h>

TaskExecute::TaskExecute() {}

void TaskExecute::defineMyTask()
{
    xTaskCreate(
        taskOne,   /* Task function. */
        "TaskOne", /* String with name of task. */
        10000,     /* Stack size in bytes. */
        NULL,      /* Parameter passed as input of the task */
        1,         /* Priority of the task. */
        NULL);      /* Task handle. */

    xTaskCreate(
        taskTwo,   /* Task function. */
        "TaskTwo", /* String with name of task. */
        10000,     /* Stack size in bytes. */
        NULL,      /* Parameter passed as input of the task */
        1,         /* Priority of the task. */
        NULL);     /* Task handle. */
}

void TaskExecute::taskToExecute()
{
}

TaskHandle_t t1 = NULL;

void TaskExecute::taskOne(void *parameter)
{

    for (int i = 0; i < 10; i++)
    {

        Serial.println("Hello from task 1");
        delay(1000);
    }

    Serial.println("Ending task 1");
    vTaskDelete(NULL);
}

void TaskExecute::taskTwo(void *parameter)
{

    for (int i = 0; i < 10; i++)
    {

        Serial.println("Hello from task 2");
        delay(1000);
    }
    Serial.println("Ending task 2");
    vTaskDelete(NULL);
}

And I got following error:
lib\TaskExecute\TaskExecute.cpp: In member function ‘void TaskExecute::defineMyTask()’:
lib\TaskExecute\TaskExecute.cpp:13:13: error: invalid use of non-static member function
NULL); /* Task handle. /
^
lib\TaskExecute\TaskExecute.cpp:21:13: error: invalid use of non-static member function
NULL); /
Task handle. */
^
*** [.pio\build\az-delivery-devkit-v4\lib6a4\TaskExecute\TaskExecute.cpp.o] Error 1

You can't use a pointer to a member function in place of a pointer to a regular function. They have different signatures. Read about it here: Standard C++

If you must have the task code be part of the class, try this:

#include "Arduino.h"

class TaskExample {
public:
	TaskExample(uint32_t ident, uint32_t period);
	void begin();

private:
	struct TaskInfo {
		uint32_t ident;
		uint32_t period;
	} info;

	static void taskToExecute(void * pvParameters);
};

void TaskExample::taskToExecute(void * pvParameters) {
	TaskInfo *info = (TaskInfo *) pvParameters;
	uint32_t ident = info->ident;
	uint32_t period = info->period;

	for (;;) {
		Serial.printf("Task %d has Executed\n", ident);
		vTaskDelay(period);
	}
}

TaskExample::TaskExample(uint32_t ident, uint32_t period) :
		info( { ident, period }) {
}

void TaskExample::begin() {
	xTaskCreatePinnedToCore(taskToExecute, "taskToExecute", 2000, (void *) &info, 3, NULL, 1);
}

TaskExample Task1(1, 500);
TaskExample Task2(2, 1000);

void setup() {
	Serial.begin(115200);
	vTaskDelay(1000);
	Task1.begin();
	Task2.begin();
}

void loop() {
}

Thank you, this is the solution

When using member functions it’s much easier to just use the C++ std::thread API, which supports executing member functions directly, without casting back and forth to void pointers:

#include <thread>

class TaskExample {
  public:
    TaskExample(uint32_t ident, uint32_t period)
      : ident(ident), period(period) {}

    void begin() {
      thread = std::thread(&TaskExample::taskToExecute, this);
    }

  private:
    uint32_t ident, period;
    std::thread thread;

    void taskToExecute() {
      for (;;) {
        Serial.printf("Task %d has Executed\n", ident);
        vTaskDelay(period);
      }
    }
};

TaskExample Task1(1, 500);
TaskExample Task2(2, 1000);

void setup() {
  Serial.begin(115200);
  vTaskDelay(1000);
  Task1.begin();
  Task2.begin();
}

void loop() {}

The default stack size is rather small, if you need a bigger stack, call esp_pthread_set_cfg before starting the threads.

When using threads, you have to watch out for data races. In general, never write to data that is shared between threads without using some kind of synchronization primitive such as an std::mutex, std::condition_variable, std::atomic, FreeRTOS queues, etc.

Pieter

PieterP:
When using member functions it's much easier to just use the C++ std::thread API

Are std::thread objects in ESP32 "compatible" with FreeRTOS tasks?

For example, do they have a handle so that they can be used with FreeRTOS features that require them (such as Task Notifications)?

Yes, they're just a layer on top of the pthreads and FreeRTOS threads. IIRC, the std::thread class only contains a handle to the underlying pthread. (sizeof(std::thread) == 4)

You can get the handle using std::thread::native_handle - cppreference.com.
I haven't tried to use notifications etc.