2nd Place - MRNC 2026 ENSMR

Robotikiyat: Top Parking

Hybrid Robot (Line Follower, Obstacle Avoider & Remote Tractor)

The Tractor

The Tractor

Servomotor hitch system

New Achievement

New Achievement

2nd Place - MRNC 2026 (1st Edition)

Project Overview

This robot is the result of hard work that led to a major new achievement: 2nd place in the very first edition of the Moroccan Robotics Challenge (MRNC 2026) at ENSMR.

Designed specifically for the "Top Parking" senior category technical challenge, this robot combines mechanical power and software flexibility. Its main mission was to tow 3D-printed trailers (empty, 150g, and up to 300g) up a 15-degree incline.

Hybrid Navigation & Hitching

The rules required strict transitions between autonomous and manual modes. The robot uses a series of TCRT5000 sensors to follow the line and detect markers (perpendicular black lines) to intelligently switch modes.

The Avoider Mode: Thanks to two ultrasonic sensors mounted in a "V" shape at the front, the robot navigates alone in a plexiglass tunnel without crossing the line, constantly correcting its trajectory using distance-keeping algorithms. Switching to Bluetooth remote control then allows steering the SG90 servos to cleanly lock the trailer into the green zone.

Robot Components

Discover the parts that made it possible to climb the ramp with a 300g payload.

ESP32 WROOM-32

Separated Wiring Table

Here is the complete mapping used during the competition. To ensure logical signal stability and avoid any ESP32 "Brownout Reset", the power supply was divided into two distinct circuits.

Origin ComponentESP32 Pin / PowerFunction
Driver L298N13, 14, 12Right Wheel (enA, in1, in2)
25, 27, 26Left Wheel (enB, in3, in4)
Array IR (TCRT5000)18, 19, 21, 22, 23Line Reading (5 Channels)
HC-SR04 (Ultrasons)Trig: 16, Echo: 35Main Sensor (Front)
Trig: 5, Echo: 17Side Sensor (Avoider Mode)
Servos SG902, 4Hitch Mechanism / Gripper
LEDsVert: 32, Rouge: 33Autonomous/Manual Signaling
⚡ Logic Power (Power Bank)5V / GNDPowers the 2 SG90 Servos
5V / GNDPowers the TCRT5000 Sensor
🔋 Motor Power (Battery)12V / GNDPowers the L298N Driver (Motors)
Buck 5VPowers ESP32 and Ultrasonics
⚠️ VERY IMPORTANT: It is imperative to connect all grounds (GND) of the Power Bank, Battery, L298N, and ESP32 together for a stable signal.

Complete Source Code (ESP32)

This code contains the complete algorithm used for the finals. It includes the PWM timer conflict fix and anti-brownout for the trailer servos.

Important: Don't forget to install the ESP32Servo library before uploading.

parking_au_top_mrnc.ino
#include "BluetoothSerial.h"
#include <ESP32Servo.h>

BluetoothSerial SerialBT;
Servo gripperServo;
Servo liftServo;

const int enA = 13;
const int in1 = 14;
const int in2 = 12;
const int enB = 25;
const int in3 = 27;
const int in4 = 26;

const int sensorPins[] = { 18, 19, 21, 22, 23 };
int s[5];

const int servoPin1 = 2;
const int servoPin2 = 4;

const int trigPin2 = 5;
const int echoPin2 = 17;
const int trigPin = 16;
const int echoPin = 35;

const int ledGreen = 32;
const int ledRed = 33;

enum RobotMode { MANUAL, AUTO };
RobotMode currentMode = MANUAL;

void setup() {
  Serial.begin(115200);

  pinMode(enA, OUTPUT);
  pinMode(in1, OUTPUT);
  pinMode(in2, OUTPUT);
  pinMode(enB, OUTPUT);
  pinMode(in3, OUTPUT);
  pinMode(in4, OUTPUT);

  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(trigPin2, OUTPUT);
  pinMode(echoPin2, INPUT);

  pinMode(ledGreen, OUTPUT);
  pinMode(ledRed, OUTPUT);

  ESP32PWM::allocateTimer(0);
  ESP32PWM::allocateTimer(1);
  ESP32PWM::allocateTimer(2);
  ESP32PWM::allocateTimer(3);

  gripperServo.setPeriodHertz(50);
  gripperServo.attach(servoPin1, 500, 2400);
  gripperServo.write(180);

  liftServo.setPeriodHertz(50);
  liftServo.attach(servoPin2, 500, 2400);
  liftServo.write(0);

  SerialBT.begin("MRNC_Yahya_Teleguide");
  Serial.println("Teleguide Mode Active. Connect your phone via Bluetooth.");
}

int df;
int dl;

void loop() {
  if (SerialBT.available()) {
    char cmd = SerialBT.read();
    Serial.print("Command: ");
    Serial.println(cmd);

    if (cmd == 'w') {
      currentMode = MANUAL;
      move(0, 0);
      Serial.println("Mode: TELEGUIDE");
    } 
    else if (cmd == 'W') {
      currentMode = AUTO;
      move(0, 0);
      Serial.println("Mode: EVITEUR/SUIVEUR");
    } 
    else if (currentMode == MANUAL) {
      digitalWrite(ledGreen, HIGH);
      digitalWrite(ledRed, LOW);
      teleguide(cmd);
    }
  }

  if (currentMode == AUTO) {
    digitalWrite(ledGreen, LOW);
    digitalWrite(ledRed, HIGH);
    autoo();
  }
}

void autoo(){
  readSensors(); 
  dl = gdl(); 
  df = gdf();

  if(dl < 20 && s[0] == 1 && s[1] == 1 && s[2] == 1 && s[3] == 1 && s[4] == 1){
    eviteur();
  }else{
    suiveur();
  }
}

void move(int L, int R) {
  digitalWrite(in1, R >= 0 ? HIGH : LOW);
  digitalWrite(in2, R >= 0 ? LOW : HIGH);
  analogWrite(enA, abs(R));

  digitalWrite(in3, L >= 0 ? HIGH : LOW);
  digitalWrite(in4, L >= 0 ? LOW : HIGH);
  analogWrite(enB, abs(L));
}

long gdf() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  long duration = pulseIn(echoPin, HIGH, 20000);
  return (duration == 0) ? 999 : (duration * 0.034 / 2);
}

long gdl() {
  digitalWrite(trigPin2, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin2, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin2, LOW);
  long duration = pulseIn(echoPin2, HIGH, 20000);
  return (duration == 0) ? 999 : (duration * 0.034 / 2);
}

void readSensors() {
  for (int i = 0; i < 5; i++) s[i] = digitalRead(sensorPins[i]);
}

void suiveur() {
  readSensors();

  if (s[2] == 0 && s[1] == 1 && s[3] == 1) {
    move(220, 220);
  } else if (s[1] == 0) {
    move(150, 220);
  } else if (s[0] == 0 && s[1] == 0) {
    move(90, 220);
  } else if (s[0] == 0 && s[1] == 0 && s[2] == 0) {
    move(0, 220);
  } else if (s[0] == 0) {
    move(-120, 220);
  } else if (s[3] == 0) {
    move(220, 150);
  } else if (s[3] == 0 && s[4] == 0) {
    move(220, 90);
  } else if (s[3] == 0 && s[4] == 0 && s[2] == 0) {
    move(220, 0);
  } else if (s[4] == 0) {
    move(220, -120);
  }
}

void teleguide(char cmd) {
  switch (cmd) {
    case 'F': move(255, 255); break;
    case 'B': move(-255, -255); break;
    case 'L': move(255, -255); break;
    case 'R': move(-255, 255); break;
    case 'G': move(255, 0); break;
    case 'I': move(0, 255); break;
    case 'S': move(0, 0); break;
    case 'v':
      gripperServo.write(180);
      Serial.println("Gripper: 0 deg");
      delay(300);
      liftServo.write(0);
      Serial.println("Lift: 40 deg");
      break;
    case 'V':
      gripperServo.write(0);
      Serial.println("Gripper: 180 deg");
      delay(300);
      liftServo.write(180);
      Serial.println("Lift: 180 deg");
      break;
  }
}

void eviteur() {
  df = gdf();
  dl = gdl();

  if (df < 15) {
    if (dl > 30) {
      move(200, -255);
    } else {
      move(-255, 200);
      delay(200);
    }
  } else {
    if (dl > 12) {
      move(200, 0);
    } else if (dl < 10) {
      move(0, 200);
    } else {
      move(200, 200);
    }
  }
}

MRNC Specifications

Core Electronics

  • Brain: ESP32 WROOM-32
  • Controller: Bluetooth (Smartphone)

Motorization

  • Motor Driver: L298N
  • Transmission: 2 Yellow TT DC Motors
  • Traction: 300g on 15° slope

Sensors

  • Line Array: TCRT5000 (5 Ch)
  • Obstacles: 2x HC-SR04 (45° Mount)

Separated Power Supply

  • Power Bank: Servos & TCRT5000
  • Battery: L298N, ESP32 & Ultrasonics

Competition Requirements

  • Dimensions: 20x15x18 cm
  • Red LED: Auto Mode
  • Green LED: Remote Mode
  • Trailers: Empty, 150g, 300g

Want to build this robot? 🚀

We have prepared a Complete Pack containing everything you need to assemble this robot from A to Z. Don't waste time looking for parts one by one!

📦 The pack contains:

  • Chassis & Mounting Brackets
  • ESP32 WROOM-32
  • Line follower sensor
  • SG90 Servomotors
  • 1 3D-printed Trailer
  • Cables and all other components

Contact us directly to order your pack: