// --- Includes and Setup from Keyestudio Examples ---
#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>
// Initialize Adafruit PWM Servo Driver (address 0x47 is common)
Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x47);
// Define sensor pins (as   per Keyestudio manual)
#define SENSOR_LEFT_PIN     6
#define SENSOR_MIDDLE_PIN   7
#define SENSOR_RIGHT_PIN    8
// Define motor speeds (0-4095,   but Adafruit library takes 0-4095 for on/off, speed
is tricky)
// For simplicity, I will use a   base speed and adjust. The manual uses ~1000-2000
for speed.
int baseSpeed = 1500; // Adjust   as needed
int turnSpeed = 1200; // Slower   speed for turns
// Sensor states
bool sensorL, sensorM, sensorR;
// --- Path Navigation State Machine ---
enum PathState {
    MOVING_UP_COL0,
    TURNING_RIGHT_AT_COL0_END,
    MOVING_RIGHT_TO_COL1,
    TURNING_RIGHT_AT_COL1_START,
    MOVING_DOWN_COL1,
    TURNING_RIGHT_AT_COL1_END,
    MOVING_RIGHT_TO_COL2,
    TURNING_LEFT_AT_COL2_START,
    MOVING_UP_COL2,
    TURNING_RIGHT_AT_COL2_END,
    MOVING_RIGHT_TO_COL3,
    TURNING_RIGHT_AT_COL3_START,
    MOVING_DOWN_COL3,
    TURNING_RIGHT_AT_COL3_END,
    MOVING_RIGHT_TO_COL4,
    TURNING_LEFT_AT_COL4_START,
    MOVING_UP_COL4,
    PATH_COMPLETE
};
PathState currentPathState = MOVING_UP_COL0;
int intersectionsCrossed = 0;
int targetIntersections = 0; // Will be set based on state
bool atJunctionFlag = false; // To handle junction once
//   --- Low-Level Motor Control Functions (from Keyestudio examples) ---
//   These functions would set the 8 PWM channels for 4 motors
//   MA: channels 6 (DIR), 7 (PWM)
//   MA1: channels 4 (DIR), 5 (PWM)
//   MB: channels 0 (DIR), 1 (PWM)
//   MB1: channels 2 (DIR), 3 (PWM)
//   DIR: 0,0,0 = stop/coast, 0,0,4095 = one way, 4,0,0 = other way (example, might
be   reversed)
//   PWM: 0,0,speed_val
void motorSet(int motorNum, bool forward, int speedVal) {
    // motorNum: 0=MB, 1=MB1, 2=MA1, 3=MA
    // This is a simplified abstraction. Refer to your wiring and the example code.
    // Example for MB (motor 0):
    // pwm.setPWM(dir_pin, 0, forward ? 4095 : 0); // Or 0 for forward, 4095 for
reverse
    // pwm.setPWM(pwm_pin, 0, speedVal);
    //   Based on Project 8 (Motor Driving) from your manual:
    //   pwm.setPWM(0,0,4095) & pwm.setPWM(1,0,speed) -> MB clockwise
    //   pwm.setPWM(0,0,0)    & pwm.setPWM(1,0,speed) -> MB anti-clockwise
    //   Similar for MB1(2,3), MA1(4,5), MA(6,7)
    // Let's define pins for   clarity (these are PWM channels, not Arduino pins)
    int MB_DIR = 0, MB_PWM =   1;
    int MB1_DIR = 2, MB1_PWM   = 3;
    int MA1_DIR = 4, MA1_PWM   = 5;
    int MA_DIR = 6, MA_PWM =   7;
    // Assuming MA/MA1 are right side, MB/MB1 are left side from driver's
perspective
    // And forward means positive speed input
    // This needs careful calibration based on your physical motor wiring and
shield behavior!
    if (motorNum == 0) { // MB (Left Front)
        pwm.setPWM(MB_DIR, 0, forward ? 4095 : 0); pwm.setPWM(MB_PWM, 0, speedVal);
    } else if (motorNum == 1) { // MB1 (Left Rear)
        pwm.setPWM(MB1_DIR, 0, forward ? 4095 : 0); pwm.setPWM(MB1_PWM, 0,
speedVal);
    } else if (motorNum == 2) { // MA1 (Right Rear)
        pwm.setPWM(MA1_DIR, 0, forward ? 4095 : 0); pwm.setPWM(MA1_PWM, 0,
speedVal);
    } else if (motorNum == 3) { // MA (Right Front)
        pwm.setPWM(MA_DIR, 0, forward ? 4095 : 0); pwm.setPWM(MA_PWM, 0, speedVal);
    }
}
void goForward(int speed) {
    // Assuming MA/MA1 are right motors, MB/MB1 are left motors
    // And all need to spin "forward"
    motorSet(0, true, speed); // MB (Left Front)
    motorSet(1, true, speed); // MB1 (Left Rear)
    motorSet(2, true, speed); // MA1 (Right Rear)
    motorSet(3, true, speed); // MA (Right Front)
}
void goBackward(int speed) {
    motorSet(0, false, speed);
    motorSet(1, false, speed);
    motorSet(2, false, speed);
    motorSet(3, false, speed);
}
void turnLeft(int speed) { // Pivot turn: Left motors back, Right motors forward
    motorSet(0, false, speed); // MB (Left Front) backward
    motorSet(1, false, speed); // MB1 (Left Rear) backward
    motorSet(2, true, speed); // MA1 (Right Rear) forward
    motorSet(3, true, speed);    // MA (Right Front) forward
}
void turnRight(int speed) { //   Pivot turn: Left motors forward, Right motors back
    motorSet(0, true, speed);    // MB (Left Front) forward
    motorSet(1, true, speed);    // MB1 (Left Rear) forward
    motorSet(2, false, speed);   // MA1 (Right Rear) backward
    motorSet(3, false, speed);   // MA (Right Front) backward
}
void stopMotors() {
    motorSet(0, true,   0);
    motorSet(1, true,   0);
    motorSet(2, true,   0);
    motorSet(3, true,   0);
}
// --- Higher-Level Movement Abstractions ---
void moveForwardSlightly() {
    goForward(baseSpeed / 2); // Slower speed to cross junction
    delay(300); // Adjust delay to move just past the center of intersection
    // stopMotors(); // Optional: stop after, or let line follower take over
}
void turnRight90() {
    // Turn right for a fixed duration/angle
    // This needs calibration!
    stopMotors();
    delay(100);
    turnRight(turnSpeed);
    delay(500); // Adjust this delay for a ~90 degree turn
    stopMotors();
    delay(100);
    intersectionsCrossed = 0; // Reset for the new segment
    atJunctionFlag = false;
}
void turnLeft90() {
    // Turn left for a fixed duration/angle
    // This needs calibration!
    stopMotors();
    delay(100);
    turnLeft(turnSpeed);
    delay(500); // Adjust this delay for a ~90 degree turn
    stopMotors();
    delay(100);
    intersectionsCrossed = 0; // Reset for the new segment
    atJunctionFlag = false;
}
// --- Sensor Reading ---
void readLineSensors() {
    // Remember: LOW = black line, HIGH = white
    sensorL = (digitalRead(SENSOR_LEFT_PIN) == LOW);
    sensorM = (digitalRead(SENSOR_MIDDLE_PIN) == LOW);
    sensorR = (digitalRead(SENSOR_RIGHT_PIN) == LOW);
}
bool isJunction() {
    // Junction detected if all three sensors see black
    return sensorL && sensorM && sensorR;
}
// --- Line Following Logic ---
void followLineBasic() {
    if (sensorM) { // Middle on line
        goForward(baseSpeed);
    } else if (sensorL && !sensorR) { // Left on line, Right is not (to avoid
issues at sharp turns)
        // Gentle turn right (slow down left motors or speed up right, or
combination)
        motorSet(0, true, baseSpeed / 2); // MB (Left Front) slower
        motorSet(1, true, baseSpeed / 2); // MB1 (Left Rear) slower
        motorSet(2, true, baseSpeed);     // MA1 (Right Rear) normal
        motorSet(3, true, baseSpeed);     // MA (Right Front) normal
    } else if (sensorR && !sensorL) { // Right on line, Left is not
        // Gentle turn left
        motorSet(0, true, baseSpeed);     // MB (Left Front) normal
        motorSet(1, true, baseSpeed);     // MB1 (Left Rear) normal
        motorSet(2, true, baseSpeed / 2); // MA1 (Right Rear) slower
        motorSet(3, true, baseSpeed / 2); // MA (Right Front) slower
    } else if (sensorL && sensorR && !sensorM) { // Both L & R on, M off (unlikely
with 1cm spacing on 2cm line, but for robustness)
        goForward(baseSpeed); // Or could be an error state
    }
    else { // Lost line (no sensors see black)
        // Could stop, or try to find line (e.g., last known direction)
        // For now, let's assume it mostly stays on track or re-centers quickly
        // Or if !sensorL && !sensorM && !sensorR
        stopMotors(); // Stop if completely lost
    }
}
void setTargetIntersectionsForState() {
    switch (currentPathState) {
        case MOVING_UP_COL0:      targetIntersections   =   2;   break;
        case MOVING_RIGHT_TO_COL1:targetIntersections   =   1;   break;
        case MOVING_DOWN_COL1:    targetIntersections   =   4;   break;
        case MOVING_RIGHT_TO_COL2:targetIntersections   =   1;   break;
        case MOVING_UP_COL2:      targetIntersections   =   4;   break;
        case MOVING_RIGHT_TO_COL3:targetIntersections   =   1;   break;
        case MOVING_DOWN_COL3:    targetIntersections   =   4;   break;
        case MOVING_RIGHT_TO_COL4:targetIntersections   =   1;   break;
        case MOVING_UP_COL4:      targetIntersections   =   4;   break; // Or to center
then stop
        default:                  targetIntersections   = 0; break; // Should not
happen in active move states
    }
}
// --- Main Setup and Loop ---
void setup() {
    Serial.begin(9600);
    pwm.begin();
    pwm.setPWMFreq(60); // Standard servo/motor frequency
    pinMode(SENSOR_LEFT_PIN, INPUT);
    pinMode(SENSOR_MIDDLE_PIN, INPUT);
    pinMode(SENSOR_RIGHT_PIN, INPUT);
    Serial.println("Robot Starting Path Navigation...");
    stopMotors();
    delay(1000);
    setTargetIntersectionsForState(); // Set initial target
}
void loop() {
    if (currentPathState == PATH_COMPLETE) {
        Serial.println("Path Complete!");
        stopMotors();
        while(true); // Halt
    }
    readLineSensors();
    if (isJunction()) {
        if (!atJunctionFlag) { // Process this junction only once
            atJunctionFlag = true; // Set flag to prevent re-entry for this same
physical junction
            Serial.print("Junction detected. State: ");
Serial.print(currentPathState);
            Serial.print(", Intersections: ");
Serial.println(intersectionsCrossed);
            stopMotors();
            delay(100); // Short pause
            moveForwardSlightly(); // Move forward to center over/past the junction
line
            intersectionsCrossed++;
            Serial.print("Crossed intersection. Total now: ");
Serial.println(intersectionsCrossed);
            if (intersectionsCrossed >= targetIntersections) {
                Serial.println("Target intersections reached for this segment.");
                // Reached end of current straight segment, decide next state/turn
                switch (currentPathState) {
                    case MOVING_UP_COL0:
                        currentPathState = TURNING_RIGHT_AT_COL0_END;
                        Serial.println("Transitioning to
TURNING_RIGHT_AT_COL0_END");
                        break;
                    case MOVING_RIGHT_TO_COL1:
                        currentPathState = TURNING_RIGHT_AT_COL1_START;
                        Serial.println("Transitioning to
TURNING_RIGHT_AT_COL1_START");
                        break;
                    case MOVING_DOWN_COL1:
                        currentPathState = TURNING_RIGHT_AT_COL1_END;
                        Serial.println("Transitioning to
TURNING_RIGHT_AT_COL1_END");
                        break;
                    case MOVING_RIGHT_TO_COL2:
                        currentPathState = TURNING_LEFT_AT_COL2_START;
                        Serial.println("Transitioning to
TURNING_LEFT_AT_COL2_START");
                         break;
                     case MOVING_UP_COL2:
                         currentPathState = TURNING_RIGHT_AT_COL2_END;
                         Serial.println("Transitioning to
TURNING_RIGHT_AT_COL2_END");
                         break;
                     case MOVING_RIGHT_TO_COL3:
                         currentPathState = TURNING_RIGHT_AT_COL3_START;
                         Serial.println("Transitioning to
TURNING_RIGHT_AT_COL3_START");
                         break;
                     case MOVING_DOWN_COL3:
                         currentPathState = TURNING_RIGHT_AT_COL3_END;
                         Serial.println("Transitioning to
TURNING_RIGHT_AT_COL3_END");
                         break;
                     case MOVING_RIGHT_TO_COL4:
                         currentPathState = TURNING_LEFT_AT_COL4_START;
                         Serial.println("Transitioning to
TURNING_LEFT_AT_COL4_START");
                         break;
                     case MOVING_UP_COL4:
                         currentPathState = PATH_COMPLETE; // End of path
                         Serial.println("Transitioning to PATH_COMPLETE");
                         break;
                     default: break;
                }
            } else {
                // Intermediate junction, continue straight
                Serial.println("Intermediate junction, continuing.");
                // atJunctionFlag will be reset when line following resumes and no
longer sees a junction
            }
        }
        // If still atJunctionFlag is true, it means I am still on the junction,
        // I might need to ensure followLineBasic() or a state transition occurs.
        // The flag is mainly to correctly count intersections.
        // Let's add a small delay to ensure sensors clear the junction before
resetting the flag.
        // The flag will naturally reset when `isJunction()` becomes false.
    } else { // Not at a junction
        atJunctionFlag = false; // Reset flag if I are off the junction
        // Handle turns or continue line following
        switch (currentPathState) {
            case TURNING_RIGHT_AT_COL0_END:
                turnRight90();
                currentPathState = MOVING_RIGHT_TO_COL1;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_RIGHT_TO_COL1");
                break;
            case TURNING_RIGHT_AT_COL1_START:
                turnRight90();
                currentPathState = MOVING_DOWN_COL1;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_DOWN_COL1");
                break;
            case TURNING_RIGHT_AT_COL1_END:
                turnRight90();
                currentPathState = MOVING_RIGHT_TO_COL2;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_RIGHT_TO_COL2");
                break;
            case TURNING_LEFT_AT_COL2_START:
                turnLeft90();
                currentPathState = MOVING_UP_COL2;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_UP_COL2");
                break;
             case TURNING_RIGHT_AT_COL2_END:
                turnRight90();
                currentPathState = MOVING_RIGHT_TO_COL3;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_RIGHT_TO_COL3");
                break;
            case TURNING_RIGHT_AT_COL3_START:
                turnRight90();
                currentPathState = MOVING_DOWN_COL3;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_DOWN_COL3");
                break;
            case TURNING_RIGHT_AT_COL3_END:
                turnRight90();
                currentPathState = MOVING_RIGHT_TO_COL4;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_RIGHT_TO_COL4");
                break;
            case TURNING_LEFT_AT_COL4_START:
                turnLeft90();
                currentPathState = MOVING_UP_COL4;
                setTargetIntersectionsForState();
                Serial.println("Turned. New state: MOVING_UP_COL4");
                break;
            // If in a moving state, just follow the line
            case MOVING_UP_COL0:
            case MOVING_RIGHT_TO_COL1:
            case MOVING_DOWN_COL1:
            case MOVING_RIGHT_TO_COL2:
            case MOVING_UP_COL2:
            case MOVING_RIGHT_TO_COL3:
            case MOVING_DOWN_COL3:
            case MOVING_RIGHT_TO_COL4:
            case MOVING_UP_COL4:
                followLineBasic();
                break;
            default: // Includes PATH_COMPLETE, do nothing
                break;
        }
    }
    delay(10); // Small delay for stability
}