/*

2022 0427
紅綠燈控制器最終版

https://wokwi.com/projects/330455834471432786



*/

#define VERSION "小姐姐講嵌入式系統設計 紅綠燈系統 最終回"

///////////////////////// Library 引入 //////////////////

#include "SerialCommand.h"



///////////////////////// 這個是常用的除錯傾印開關 //////////////////
//////////////////////// 0 不印 , 1 要印  ////////////////////////
#define DEBUG 0
#if (DEBUG)
#define DEBUGP(x) Serial.print(x)
#define DEBUGN(x) Serial.println(x)
#else
#define DEBUGP(x)
#define DEBUGN(x)
#endif

/////////////////////////控制程式更新率的一些變數 與宣告  //////////////////
// HZ Control
#define HZ_SETTING 2 /// 讓系統一秒跑5次即可
int mainLoop_count;
unsigned long fast_loopTimer; // Time in miliseconds of main control loop
const int hzCount = (1000 / HZ_SETTING) - 1;
const int timeRemind = 1000 / HZ_SETTING;

/////////////////////////硬體腳位相關的定義 //////////////////

#define LED_Rabbit_RED 11
#define LED_Rabbit_YELLOW 12
#define LED_Rabbit_GREEN 7
#define LED_Tortoise_RED 8
#define LED_Tortoise_YELLOW 9
#define LED_Tortoise_GREEN 10
#define LED_PIN 13

#define SW_MODE A0
#define SW_LIGHT_CONTROL A1
#define KNOB_TIME_Setting A2

/////////////////////////Golbal 變數  //////////////////////////////////////////////////////

int secCount = 0;

byte flagShow=0; 

/////////////////////////CLI 相關  //////////////////////////////////////////////////////

SerialCommand SCmd; // The demo SerialCommand object

void process_command()
{
  int aNumber, bNumber, cNumber;
  char *arg;
  Serial.println("We're in process_command");

/// Para 1

  arg = SCmd.next();
  if (arg != NULL)
  {
    aNumber = atoi(arg); // Converts a char string to an integer
    Serial.print("First argument was: ");
    Serial.println(aNumber);
  }
  else
  {
    Serial.println("No 1 st arguments");
    aNumber= 9999;
  }


/// Para 2


  arg = SCmd.next();
  if (arg != NULL)
  {
    bNumber = atoi(arg); // Converts a char string to an integer
    Serial.print("2 nd argument was: ");
    Serial.println(bNumber);
  }
  else
  {
    Serial.println("No 2nd arguments");
    bNumber= 9999;
  }

 
/// Para 3

  arg = SCmd.next();
  if (arg != NULL)
  {
    cNumber = atoi(arg); // Converts a char string to an integer
    Serial.print("3rd argument was: ");
    Serial.println(cNumber);
  }
  else
  {
    Serial.println("No 3rd  arguments");
    cNumber= 9999;
  }

    switch(aNumber){


      case 0:
      Serial.println("CMD 0");

      break;


      case 4:

      if(bNumber==9999 ){
        return;
      }

      flagShow= bNumber;


      break;


      case 104:

       Serial.println(VERSION);


      default:
       Serial.println("Default");


      break;


    } 


}

void unrecognized(const char *command)
{
  Serial.println("What?");
}




///////////////////////// 紅綠燈的秒數宣告 ///////////////////////////////////////////////////

const int GREEN_LIGHT = 12; // Green light time in seconds
const int AMBER_LIGHT = 3;  // Amber light time in seconds
const int DEAD_LIGHT = 2;   // Dead light time in seconds

int trafficLightTimeTable[6] = {GREEN_LIGHT * HZ_SETTING,
                                AMBER_LIGHT *HZ_SETTING,
                                DEAD_LIGHT *HZ_SETTING,
                                (GREEN_LIGHT / 3) * HZ_SETTING,
                                AMBER_LIGHT *HZ_SETTING,
                                DEAD_LIGHT *HZ_SETTING};

///////////////////////// 狀態機宣告  ///////////////////////////////////////////////////

#define STATE_RABBIT_G_TORTO_R 0
#define STATE_RABBIT_Y_TORTO_R 1
#define STATE_RABBIT_R_TORTO_R 2
#define STATE_RABBIT_R_TORTO_G 3
#define STATE_RABBIT_R_TORTO_Y 4
#define STATE_RABBIT_R2_TORTO_R2 5

#define STATE_MANUAL_RABBIT_G 10
#define STATE_MANUAL_RABBIT_R 11

#define STATE_REMOTE_RABBIT_G 20
#define STATE_REMOTE_RABBIT_R 21
#define STATE_REMOTE_FLASH_MODE 22

int state = 0;
int lastState = -1;
int timeCounter;

void showState()
{

    switch (state)
    {

    case STATE_RABBIT_G_TORTO_R:

        Serial.println("STATE_RABBIT_G_TORTO_R");

        break;

    case STATE_RABBIT_Y_TORTO_R:

        Serial.println("STATE_RABBIT_Y_TORTO_R");

        break;

    case STATE_RABBIT_R_TORTO_R:

        Serial.println("STATE_RABBIT_R_TORTO_R");

        break;

    case STATE_RABBIT_R_TORTO_G:

        Serial.println("STATE_RABBIT_R_TORTO_G");
        break;

    case STATE_RABBIT_R_TORTO_Y:

        Serial.println("STATE_RABBIT_R_TORTO_Y");

        break;

    case STATE_RABBIT_R2_TORTO_R2:

        Serial.println("STATE_RABBIT_R2_TORTO_R2");

        break;

    case STATE_MANUAL_RABBIT_G:

        Serial.println("STATE_MANUAL_RABBIT_G");

        break;

    case STATE_MANUAL_RABBIT_R:

        Serial.println("STATE_MANUAL_RABBIT_R");

        break;
    }
}

void doTrafficLightTask()
{

    switch (state)
    {

    case STATE_RABBIT_G_TORTO_R:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            timeCounter = trafficLightTimeTable[state];
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, LOW);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, HIGH);
            digitalWrite(LED_Tortoise_RED, HIGH);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, LOW);
        }

        timeCounter = timeCounter - 1;
        if (timeCounter == 0)
        {
            state = 1;
            return;
        }

        break;

    case STATE_RABBIT_Y_TORTO_R:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            timeCounter = trafficLightTimeTable[STATE_RABBIT_Y_TORTO_R];
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, LOW);
            digitalWrite(LED_Rabbit_YELLOW, HIGH);
            digitalWrite(LED_Rabbit_GREEN, LOW);
            digitalWrite(LED_Tortoise_RED, HIGH);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, LOW);
        }

        digitalWrite(LED_Rabbit_YELLOW, !digitalRead(LED_Rabbit_YELLOW));

        timeCounter = timeCounter - 1;
        if (timeCounter == 0)
        {
            state = 2;
        }

        break;

    case STATE_RABBIT_R_TORTO_R:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            timeCounter = trafficLightTimeTable[STATE_RABBIT_R_TORTO_R];
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, HIGH);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, LOW);
            digitalWrite(LED_Tortoise_RED, HIGH);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, LOW);
        }

        timeCounter = timeCounter - 1;
        if (timeCounter == 0)
        {
            state = 3;
        }

        break;

    case STATE_RABBIT_R_TORTO_G:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            timeCounter = trafficLightTimeTable[state];
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, HIGH);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, LOW);
            digitalWrite(LED_Tortoise_RED, LOW);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, HIGH);
        }

        timeCounter = timeCounter - 1;
        if (timeCounter == 0)
        {
            state = 4;
        }

        break;

    case STATE_RABBIT_R_TORTO_Y:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            timeCounter = trafficLightTimeTable[state];
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, HIGH);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, LOW);
            digitalWrite(LED_Tortoise_RED, LOW);
            digitalWrite(LED_Tortoise_YELLOW, HIGH);
            digitalWrite(LED_Tortoise_GREEN, LOW);
        }

        timeCounter = timeCounter - 1;
        digitalWrite(LED_Tortoise_YELLOW, !digitalRead(LED_Tortoise_YELLOW));
        if (timeCounter == 0)
        {
            state = 5;
        }

        break;

    case STATE_RABBIT_R2_TORTO_R2:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            timeCounter = trafficLightTimeTable[state];
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, HIGH);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, LOW);
            digitalWrite(LED_Tortoise_RED, HIGH);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, LOW);
        }

        timeCounter = timeCounter - 1;
        if (timeCounter == 0)
        {
            state = STATE_RABBIT_G_TORTO_R;
        }

        break;

    case STATE_MANUAL_RABBIT_G:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, LOW);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, HIGH);
            digitalWrite(LED_Tortoise_RED, HIGH);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, LOW);
        }

        break;

    case STATE_MANUAL_RABBIT_R:

        if (lastState != state)
        {
            Serial.print("State Change to ");
            Serial.println(state);
            showState();

            lastState = state;
            secCount = 0;

            digitalWrite(LED_Rabbit_RED, HIGH);
            digitalWrite(LED_Rabbit_YELLOW, LOW);
            digitalWrite(LED_Rabbit_GREEN, LOW);
            digitalWrite(LED_Tortoise_RED, LOW);
            digitalWrite(LED_Tortoise_YELLOW, LOW);
            digitalWrite(LED_Tortoise_GREEN, HIGH);
        }

        break;
    }
}

void hwInit()
{

    Serial.begin(115200);
    // initialize digital pin LED_BUILTIN as an output.

    pinMode(LED_Rabbit_RED, OUTPUT);
    pinMode(LED_Rabbit_YELLOW, OUTPUT);
    pinMode(LED_Rabbit_GREEN, OUTPUT);
    pinMode(LED_Tortoise_RED, OUTPUT);
    pinMode(LED_Tortoise_YELLOW, OUTPUT);
    pinMode(LED_Tortoise_GREEN, OUTPUT);
    pinMode(LED_PIN, OUTPUT);

    digitalWrite(LED_Rabbit_RED, LOW);
    digitalWrite(LED_Rabbit_YELLOW, LOW);
    digitalWrite(LED_Rabbit_GREEN, LOW);
    digitalWrite(LED_Tortoise_RED, LOW);
    digitalWrite(LED_Tortoise_YELLOW, LOW);
    digitalWrite(LED_Tortoise_GREEN, LOW);

    Serial.println(VERSION);
}

int adcReading = 0;
int adcReadingLast = -1;

byte modeSw = 0;
byte mmodeSwLast = 10;

//// 這邊的使用者輸入接收方法,僅供模擬用,實際不要這樣寫,會出問題。
void doGetUserInput()
{

    /// 讀取時間設定旋鈕
    adcReading = map(analogRead(KNOB_TIME_Setting), 0, 1023, 1, 5);
    if (adcReading != adcReadingLast)
    {

        Serial.print(" ------------ 時間間隔調整 X ");
        Serial.println(adcReading);
        delay(1000); // 停下來一下,不然看不到

        adcReadingLast = adcReading;
        trafficLightTimeTable[0] = GREEN_LIGHT * HZ_SETTING * adcReading;
        trafficLightTimeTable[1] = AMBER_LIGHT * HZ_SETTING * adcReading;
        trafficLightTimeTable[2] = DEAD_LIGHT * HZ_SETTING * adcReading;
        trafficLightTimeTable[3] = (GREEN_LIGHT / 3) * HZ_SETTING * adcReading;
        trafficLightTimeTable[4] = AMBER_LIGHT * HZ_SETTING * adcReading;
        trafficLightTimeTable[5] = DEAD_LIGHT * HZ_SETTING * adcReading;
    }

    ////讀取自動,手動切換開關
    modeSw = digitalRead(SW_MODE);
    if (modeSw == 1)
    {

        if (mmodeSwLast != modeSw)
        {
            mmodeSwLast = modeSw;
            Serial.println("Change to Manual Mode");
            delay(1000); // 方便我們觀察狀態變換
        }

        if (digitalRead(SW_LIGHT_CONTROL) == HIGH)
        {

            state = STATE_MANUAL_RABBIT_G;
        }
        else
        {

            state = STATE_MANUAL_RABBIT_R;
        }
    }
    else if (modeSw == 0)
    {

        if (mmodeSwLast != modeSw)
        {
            mmodeSwLast = modeSw;
            Serial.println("Change to Auto Mode");
            delay(1000);                    // 方便我們觀察狀態變換
            state = STATE_RABBIT_G_TORTO_R; /// 從新來過 ...
        }
    }
}


//////////////////////////////////  開始工作了 //////////////////////////////////////////////////////////////

void setup()
{

    hwInit();
    
    SCmd.addCommand("P", process_command); // Converts two arguments to integers and echos them back
    SCmd.setDefaultHandler(unrecognized);  // Handler for command that isn't matched  (says "What?")
}

// the loop function runs over and over again forever
void loop()
{

    /// 放在這邊會有最快的回應    
      SCmd.readSerial();

    /// 利用這個機制來管理時間,我們可以掌握到的時間間隔就是   1/HZ_SETTING  (秒)
    /// 以這個例子來說, HZ_SETTING = 5 , 所以每1/5 秒 會進入到這個環圈1次
    if (millis() - fast_loopTimer > hzCount)
    {
        fast_loopTimer = millis();
        mainLoop_count++;

        doGetUserInput();
        doTrafficLightTask();

        //// 這個是方便我們使用的一個判斷,因為我們是設計系統每 1/5 秒進入迴圈一次,
        //// 所以每進來5次就是 1 秒

        if (mainLoop_count % HZ_SETTING == 0)
        {
            secCount++;
            mainLoop_count = 0;

            if(flagShow&0x1 ==1 ){
                Serial.print(" S :");
                Serial.println(secCount);
            }
            
            
            digitalWrite(LED_PIN, !digitalRead(LED_PIN));

            /// 這裡我們故意浪費100ms ,看看下面的時間計算是否正確??
            /// delay(100);
        }

        // 在這裡我們來計算一次,這次跑進來總共用了多少時間,
        /// 然後我們還有多少時間可以用
        
        if(flagShow & 0x2 ==0x2){

          Serial.print("一個迴圈是");
          Serial.print(timeRemind);
          Serial.print("ms,我還有剩下這麼多時間喔 :");
          Serial.print(timeRemind - (millis() - fast_loopTimer));
          Serial.print(" ms ");
        }

    }
}