/*
   每个示例集都必须有一个LED闪烁示例
   对于这个示例，我的想法是尽可能多地展示
   LED闪烁的方法。所以，我们开始吧。

   测试通过的硬件：
   - Arduino Nano
   - ESP8266
   - ESP32
   - STM32 Maple Mini
*/

// ----------------------------------------
// 以下"宏定义"在编译时控制库的功能，
// 应该根据需要的功能在主程序中使用
//
// #define _TASK_TIMECRITICAL       // 启用调度超时监控
#define _TASK_SLEEP_ON_IDLE_RUN  // 如果在运行过程中没有调用回调方法，启用1ms SLEEP_IDLE省电模式
#define _TASK_STATUS_REQUEST     // 编译时支持状态请求功能 - 除了时间触发外，还支持状态改变事件触发任务
// #define _TASK_WDT_IDS            // 编译时支持看门狗控制点和任务ID
// #define _TASK_LTS_POINTER        // 编译时支持本地任务存储指针
// #define _TASK_PRIORITY           // 支持分层调度优先级
// #define _TASK_MICRO_RES          // 支持微秒级分辨率
// #define _TASK_STD_FUNCTION       // 支持 std::function 
// #define _TASK_DEBUG              // 为了调试目的，使所有方法和变量公开
// #define _TASK_INLINE             // 使所有方法"内联" - 需要支持某些多标签、多文件实现
// #define _TASK_TIMEOUT            // 支持整体任务超时
// #define _TASK_OO_CALLBACKS       // 通过继承支持回调
// #define _TASK_EXPOSE_CHAIN       // 访问任务链中任务的方法
// #define _TASK_SCHEDULING_OPTIONS // 支持多种调度选项
// #define _TASK_THREAD_SAFE        // 启用线程安全的额外检查
// #define _TASK_SELF_DESTRUCT      // 启用任务在禁用后"自毁"
#include <TaskScheduler.h>  // 包含TaskScheduler库

// 调试和测试选项
#define _DEBUG_  // 启用调试模式
//#define _TEST_   // 启用测试模式

#ifdef _DEBUG_
#define _PP(a) Serial.print(a);   // 打印内容
#define _PL(a) Serial.println(a); // 打印并换行
#else
#define _PP(a)   // 空定义，不执行任何操作
#define _PL(a)   // 空定义，不执行任何操作
#endif

// LED_BUILTIN  13
#if defined( ARDUINO_ARCH_ESP32 )
#define LED_BUILTIN  23 // ESP32开发板2没有内置LED
#endif

// 调度器
Scheduler ts;  // 创建调度器对象

/*
   方法1：LED由布尔变量驱动；false = 关闭，true = 开启
*/
#define PERIOD1 500     // 周期500毫秒
#define DURATION 10000  // 持续时间10秒
void blink1CB();  // 任务1回调函数声明
Task tBlink1 ( PERIOD1 * TASK_MILLISECOND, DURATION / PERIOD1, &blink1CB, &ts, true );  // 任务1：每500ms运行一次，运行20次

/*
   方法2：两个回调方法：一个开启LED，另一个关闭LED
*/
#define PERIOD2 400     // 周期400毫秒
void blink2CB_ON();   // 开启LED回调函数
void blink2CB_OFF();  // 关闭LED回调函数
Task tBlink2 ( PERIOD2 * TASK_MILLISECOND, DURATION / PERIOD2, &blink2CB_ON, &ts, false );  // 任务2：每400ms运行一次，运行25次

/*
   方法3：使用运行计数器
*/
#define PERIOD3 300     // 周期300毫秒
void blink3CB();  // 任务3回调函数
Task tBlink3 (PERIOD3 * TASK_MILLISECOND, DURATION / PERIOD3, &blink3CB, &ts, false);  // 任务3：每300ms运行一次，运行33次

/*
   方法4：使用状态请求对象在任务之间传递控制
*/
#define PERIOD4 200     // 周期200毫秒
bool blink41OE();  // 任务4开启使能函数
void blink41();    // 任务4开启回调函数
void blink42();    // 任务4关闭回调函数
void blink42OD();  // 任务4关闭禁用函数
Task tBlink4On  ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink41, &ts, false, &blink41OE );    // 任务4开启：运行一次
Task tBlink4Off ( PERIOD4 * TASK_MILLISECOND, TASK_ONCE, blink42, &ts, false, NULL, &blink42OD );  // 任务4关闭：运行一次


/*
   方法5：两个交错的任务
*/
#define PERIOD5 600     // 周期600毫秒
bool blink51OE();  // 任务5开启使能函数
void blink51();    // 任务5开启回调函数
void blink52();    // 任务5关闭回调函数
void blink52OD();  // 任务5关闭禁用函数
Task tBlink5On  ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink51, &ts, false, &blink51OE );   // 任务5开启：每600ms运行一次，运行16次
Task tBlink5Off ( 600 * TASK_MILLISECOND, DURATION / PERIOD5, &blink52, &ts, false, NULL, &blink52OD );  // 任务5关闭：每600ms运行一次，运行16次


/*
   方法6：基于运行计数器的随机间隔
*/
#define PERIOD6 300     // 周期300毫秒
void blink6CB();   // 任务6回调函数
bool blink6OE();   // 任务6使能函数
void blink6OD();   // 任务6禁用函数
Task tBlink6 ( PERIOD6 * TASK_MILLISECOND, DURATION / PERIOD6, &blink6CB, &ts, false, &blink6OE, &blink6OD );  // 任务6：每300ms运行一次，运行33次

void setup() {
  // 在这里放置初始化代码，运行一次：
#if defined(_DEBUG_) || defined(_TEST_)
  Serial.begin(115200);  // 初始化串口通信，波特率115200
  delay(TASK_SECOND);    // 延时1秒，等待串口稳定
  _PL("TaskScheduler Blink example");  // 打印示例标题
  _PL("Blinking for 10 seconds using various techniques\n");  // 打印说明：使用各种技术闪烁10秒
  delay(2 * TASK_SECOND);  // 延时2秒，让用户阅读信息
#endif
  pinMode(LED_BUILTIN, OUTPUT);  // 设置LED引脚为输出模式
}

void loop() {
  ts.execute();  // 执行调度器，运行所有已安排的任务
}

inline void LEDOn() {
  digitalWrite( LED_BUILTIN, HIGH );  // 打开LED（输出高电平）
}

inline void LEDOff() {
  digitalWrite( LED_BUILTIN, LOW );   // 关闭LED（输出低电平）
}

// === 方法1 =======================================
bool LED_state = false;  // LED状态变量，false=关闭，true=开启
void blink1CB() {
  if ( tBlink1.isFirstIteration() ) {
    _PP(millis());  // 打印当前时间
    _PL(": Blink1 - simple flag driven");  // 打印方法1说明：简单的标志位驱动
    LED_state = false;  // 初始化LED状态为关闭
  }

  if ( LED_state ) {
    LEDOff();      // 如果LED是开启状态，则关闭它
    LED_state = false;  // 更新状态为关闭
  }
  else {
    LEDOn();       // 如果LED是关闭状态，则开启它
    LED_state = true;   // 更新状态为开启
  }

  if ( tBlink1.isLastIteration() ) {
    tBlink2.restartDelayed( 2 * TASK_SECOND );  // 2秒后启动方法2的任务
    LEDOff();  // 确保LED最终是关闭状态
  }
}


// === 方法2 ======================================
void blink2CB_ON() {
  if ( tBlink2.isFirstIteration() ) {
    _PP(millis());  // 打印当前时间
    _PL(": Blink2 - 2 callback methods");  // 打印方法2说明：两个回调方法
  }

  LEDOn();  // 开启LED
  tBlink2.setCallback( &blink2CB_OFF );  // 设置下一次回调为关闭函数

  if ( tBlink2.isLastIteration() ) {
    tBlink3.restartDelayed( 2 * TASK_SECOND );  // 2秒后启动方法3的任务
    LEDOff();  // 确保LED最终是关闭状态
  }
}


void blink2CB_OFF() {
  LEDOff();  // 关闭LED
  tBlink2.setCallback( &blink2CB_ON );  // 设置下一次回调为开启函数

  if ( tBlink2.isLastIteration() ) {
    tBlink3.restartDelayed( 2 * TASK_SECOND );  // 2秒后启动方法3的任务
    LEDOff();  // 确保LED最终是关闭状态
  }
}


// === 方法3 =====================================
void blink3CB() {
  if ( tBlink3.isFirstIteration() ) {
    _PP(millis());  // 打印当前时间
    _PL(": Blink3 - Run Counter driven");  // 打印方法3说明：运行计数器驱动
  }

  if ( tBlink3.getRunCounter() & 1 ) {
    LEDOn();  // 如果运行计数器是奇数，开启LED
  }
  else {
    LEDOff();  // 如果运行计数器是偶数，关闭LED
  }

  if ( tBlink3.isLastIteration() ) {
    tBlink4On.setOnEnable( &blink41OE );  // 设置方法4开启任务的使能函数
    tBlink4On.restartDelayed( 2 * TASK_SECOND );  // 2秒后启动方法4的任务
    LEDOff();  // 确保LED最终是关闭状态
  }
}


// === 方法4 =====================================
void blink41OE() {
  _PP(millis());  // 打印当前时间
  _PL(": Blink4 - OnEnable method");  // 打印方法4说明：使能方法
}

void blink4CB_ON() {
  LEDOn();      // 开启LED
  tBlink4On.disable();   // 禁用开启任务
  tBlink4Off.enable();   // 启用关闭任务
}

void blink4CB_OFF() {
  LEDOff();     // 关闭LED
  tBlink4Off.disable();  // 禁用关闭任务
  tBlink4On.enable();    // 启用开启任务

  if ( tBlink4Off.isLastIteration() ) {
    tBlink5.restartDelayed( 2 * TASK_SECOND );  // 2秒后启动方法5的任务
    tBlink4On.disable();  // 禁用开启任务，停止闪烁
  }
}


// === 方法5 ==========================================
bool blink51OE() {
  _PP(millis());  // 打印当前时间
  _PL(": Blink5 - Two interleaving tasks");  // 打印方法5说明：两个交错的任务
  tBlink5On.setOnEnable( NULL );  // 清除使能函数，只运行一次
  return true;  // 返回true表示任务可以运行
}
void blink51() {
  //  _PP(millis());
  //  _PL(": blink51");
  LEDOn();  // 开启LED
}
void blink52() {
  //  _PP(millis());
  //  _PL(": blink52");
  LEDOff();  // 关闭LED
}
void blink52OD() {
  tBlink6.restartDelayed( 2 * TASK_SECOND );  // 2秒后启动方法6的任务
  LEDOff();  // 确保LED最终是关闭状态
}


// === 方法6 ============================================
long interval6 = 0;  // 随机间隔变量，用于存储随机的LED开启时间
bool blink6OE() {
  _PP(millis());  // 打印当前时间
  _PP(": Blink6 - RunCounter + Random ON interval = ");  // 打印方法6说明：运行计数器+随机开启间隔
  interval6 = random( 100, 901 );  // 生成100-900毫秒之间的随机数作为LED开启时间
  tBlink6.setInterval( interval6 );  // 设置任务间隔为随机生成的开启时间
  _PL( interval6 );  // 打印随机间隔值
  tBlink6.delay( 2 * TASK_SECOND );  // 延时2秒后开始执行

  return true;  // 返回true表示任务可以运行
}

void blink6CB() {
  if ( tBlink6.getRunCounter() & 1 ) {
    LEDOn();  // 如果运行计数器是奇数，开启LED
    tBlink6.setInterval( interval6 );  // 设置LED开启的持续时间为随机值
  }
  else {
    LEDOff();  // 如果运行计数器是偶数，关闭LED
    tBlink6.setInterval( TASK_SECOND - interval6 );  // 设置LED关闭的持续时间为1秒减去开启时间
  }
}

void blink6OD() {
  tBlink1.restartDelayed( 2 * TASK_SECOND );  // 2秒后重新开始方法1的任务，形成循环
  LEDOff();  // 确保LED最终是关闭状态
}
