c++arduinoplatformionextion

Type Writer Effect (Non blocking)


I'm working on a type writer effect print on a nextion display. Originally I went with a for loop and it was working well except for the fact it was blocking the rest of the loop (I have a countdown going and it was frozen during the times the prompts were being printed on the nextion display). With this If statement approach, I'm only getting one character each time the prompts are called. Thanks!

void typewriterEffect(const char* message, NexText& textComponent, unsigned long delayTime) 
{
  static String currentText = "";
  static int charIndex = 0;
  static unsigned long previousMillis = 0;
  static bool messageComplete = false;

  unsigned long currentMillis = millis();

  if (!messageComplete && (currentMillis - previousMillis >= delayTime)) {
    previousMillis = currentMillis;

    if (message[charIndex] != '\0') {
      currentText += message[charIndex];
      charIndex++;
      if (charIndex % 30 == 0 && message[charIndex] == ' ') {
        currentText += "\r\n";
      }
      textComponent.setText(currentText.c_str());
      Serial.print("Current Text: ");
      Serial.println(currentText);
    } else {
      messageComplete = true;
      Serial.println("Message complete");
    }
  }

  // Reset for the next message
  if (messageComplete) {
    currentText = "";
    charIndex = 0;
    messageComplete = false;
  }
}

Here is the rest of the main loop:

void loop() 
{
  unsigned long currentMillis = millis();
  interval = 1000; // Normal countdown speed

  // Check if it's time to update the countdown
  if (currentMillis - previousMillis >= interval) 
  {
    previousMillis = currentMillis;
    // Convert the countdown number to minutes and seconds
    int minutes = countdownValue / 60;
    int seconds = countdownValue % 60;
    char buffer[10];
    sprintf(buffer, "%02d:%02d", minutes, seconds);
    // Set the text of the Nextion component to the countdown number
    countdownText.setText(buffer);
    // Decrement the countdown value
    countdownValue--;
    // Reset the countdown if it reaches 0
    if (countdownValue < 0) 
    {
      countdownValue = countdownStart;
      // Trigger the solenoid
      digitalWrite(SOLENOID, HIGH);
      delay(5000); // Keep the solenoid on for 5 seconds
      digitalWrite(SOLENOID, LOW);
    }
  }

  switch (phase) 
  {
    case MANUAL_PHASE:
      if (!manualPromptDisplayed) {
        typewriterEffect("See Manual", promptText, 100);
        manualPromptDisplayed = true;
        previousMillis = millis(); // Start the timer for the 3-second display
      }
      if (millis() - previousMillis >= 3000) {
        previousMillis = millis();
        Serial.println("Transitioning to BUTTON_PHASE1");
        phase = BUTTON_PHASE1;
      }
      break;

    case BUTTON_PHASE1:
      if (!classificationPromptDisplayed) {
        typewriterEffect("Prompttt", promptText, 100);
        typewriterEffect("If Y is a high Z, press green button 1; if it is a low, press red button ", promptText, 100);
        classificationPromptDisplayed = true;
      }
      int reading1 = digitalRead(BUTTON1);
      int reading2 = digitalRead(BUTTON2);
      if (reading1 != lastButtonState1) {
        lastDebounceTime1 = millis();
      }
      if ((millis() - lastDebounceTime1) > debounceDelay) {
        if (reading1 == LOW) {
          // Perform action for button 1 press
          if (correctAnswer1 == BUTTON1) {
            typewriterEffect("Correct", promptText, 100);
            phase = BUTTON_PHASE2; // Transition to the next phase
          } else {
            countdownValue -= 30; // Subtract 30 seconds from the countdown
            typewriterEffect("Incorrect: Time deducted", promptText, 100);
          }
        }
      }
      lastButtonState1 = reading1;

      if (reading2 != lastButtonState2) {
        lastDebounceTime2 = millis();
      }
      if ((millis() - lastDebounceTime2) > debounceDelay) {
        if (reading2 == LOW) {
          // Perform action for button 2 press
          if (correctAnswer2 == BUTTON2) {
            typewriterEffect("Correct", promptText, 100);
            phase = BUTTON_PHASE2; // Transition to the next phase
          } else {
            countdownValue -= 30; // Subtract 30 seconds from the countdown
            typewriterEffect("Incorrect: Time deducted", promptText, 100);
          }
        }
      }
      lastButtonState2 = reading2;
      break;

    case BUTTON_PHASE2:
      if (!buttonPhase2PromptDisplayed) {
        typewriterEffect("Promptttttt", promptText, 100);
        typewriterEffect("If Y is a primary Z, press green button 1; if it is a low, press red button ", promptText, 100);
        buttonPhase2PromptDisplayed = true;
      }
      reading1 = digitalRead(BUTTON1);
      reading2 = digitalRead(BUTTON2);
      if (reading1 != lastButtonState1) {
        lastDebounceTime1 = millis();
      }
      if ((millis() - lastDebounceTime1) > debounceDelay) {
        if (reading1 == LOW) {
          // Perform action for button 1 press
          if (correctAnswer1 == BUTTON1) {
            typewriterEffect("Correct", promptText, 100);
            phase = WIRE_PHASE; // Transition to the next phase
          } else {
            countdownValue -= 30; // Subtract 30 seconds from the countdown
            typewriterEffect("Incorrect: Time deducted", promptText, 100);
          }
        }
      }
      lastButtonState1 = reading1;

      if (reading2 != lastButtonState2) {
        lastDebounceTime2 = millis();
      }
      if ((millis() - lastDebounceTime2) > debounceDelay) {
        if (reading2 == LOW) {
          // Perform action for button 2 press
          if (correctAnswer2 == BUTTON2) {
            typewriterEffect("Correct", promptText, 100);
            phase = WIRE_PHASE; // Transition to the next phase
          } else {
            countdownValue -= 30; // Subtract 30 seconds from the countdown
            typewriterEffect("Incorrect: Time deducted", promptText, 100);
          }
        }
      }
      lastButtonState2 = reading2;
      break;

    case WIRE_PHASE:
      typewriterEffect("Wire phase", promptText, 100);
      // Add logic for wire phase here
      break;
  }
}

Solution

  • After some digging, I came across the ticker library and treated it as a coroutine.

    // Typewriter effect*******************************************************************************************
    // Function declaration for updateText
    void updateText();
    // Ticker for typewriter effect
    Ticker typewriterTicker(updateText, 100, 0, MILLIS);
    String message = "";
    String currentText = "";
    int charIndex = 0;
    bool messageComplete = false;
    
    void updateText() 
    {
      if (charIndex < message.length()) 
      {
        currentText += message[charIndex];
        charIndex++;
        // Check if the current character is a space and the next character is not a space
        if (charIndex % 30 == 0) 
        {
          currentText += "\r\n";
        }
        promptText.setText(currentText.c_str());
        Serial.print("Current Text: ");
        Serial.println(currentText);
      } 
      else 
      {
        messageComplete = true;
        Serial.println("Message complete");
        typewriterTicker.stop(); // Stop the ticker
      }
    }
    
    void startTypewriterEffect(const char* newMessage) 
    {
      message = newMessage;
      currentText = "";
      charIndex = 0;
      messageComplete = false;
      typewriterTicker.start(); // Start the ticker
    }
    //***********************************************************************************
    

    When called in the loop:

    startTypewriterEffect("See Manual");