Switching between Autonomous and Manual Mode i2c Arduino / Python

Hi;

I have built a robot that started off as an Arduino based collision avoidance robot. Then I turned it into a manually remote controlled robot by adding a Raspberry Pi which has a Web Cam and is used to serve a web page which is used as the remote control panel.

The Arduino (Nano ATMega328P) and the Pi communicate over a two wire i2c Serial interface

What I would like to do now is provide the ability to automatically (and possibly manually) switch between manual and autonomous mode.

To do this I was thinking of adding some kind of statement that checks to see if Serial Data is being received from the Pi (i.e. a command has been sent from the web page) and if it is not switch to autonomous mode?

What would be the best way to do this?

Here is my existing program for the Nano:

// Arduino Pin A5 to RPi SDA
// Arduino Pin A6 to RPi SCL
// Inspiration from Oscar Laing's Blog http://blog.oscarliang.net/raspberry-pi-arduino-connected-i2c

#include <Wire.h>
#include <Servo.h>

#define SLAVE_ADDRESS 0x04
int number = 0;
int state = 0;

// Setup Ultrasonic Sensor Pins
#define trigPin 12
#define echoPin 13

// Defines for reading distances and collision avoidance when in autonomous mode (when setup) 
#define microsecondsToCentimeters(microseconds) (unsigned long)microseconds / 29.1 / 2.0
#define MinActionDistance 40 // Set maximum allowaable distance to obstacle

// Setup motor controller pins for L298N
// Right Motor
int enA = 11;
int in1 = 8;
int in2 = 7;

//Left Motor
int enB = 10;
int in3 = 6;
int in4 = 5;

// Setup LED test pin
int Led = 3;

// Setup the servo
Servo myservo;

byte sweep_pos = 0;
byte pos_index = 90;
unsigned long left_dist, right_dist;

//Set unsigned integer variables to be used
unsigned int duration;
unsigned int distance;
unsigned int FrontDistance;
unsigned int LeftDistance;
unsigned int RightDistance;


void setup() {
  
  Serial.begin(9600); // Start serial for output
  Wire.begin(SLAVE_ADDRESS); // Initialise i2c as slave
 
  //Define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  
  //Setup the servo
  myservo.attach(2); //Attaches the servo to pin 2 of the Nano
  myservo.write(90); //Set the servo to face front
  sweep_pos = 0;
  
  //Below set all the pin modes as OUTPUT as they will all be outputting data
  //Set modes for distance sensor pins 
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  
  //Set modes for motor controller pins
  pinMode(enA, OUTPUT);   
  pinMode(enB, OUTPUT);   
  pinMode(in1, OUTPUT);   
  pinMode(in2, OUTPUT);   
  pinMode(in3, OUTPUT);   
  pinMode(in4, OUTPUT);

  //Set mode for Led test pin
  pinMode(Led, OUTPUT);

  // Declare ready
  Serial.println("Hammerstein Ready!");
}

void loop() {
  delay(100);
  unsigned long dist_fwd;
  //Rotate the sensor to check for obstacles
  //scan(); //Call the scan function
  ping();
  
}

// Callback for received data
void receiveData(int byteCount) {
  
  while(Wire.available()) {
    number = Wire.read();
    Serial.println("data received: ");
    Serial.println(number);
    
    if (number == 0) {
      Stop();
    } 
    if (number == 1) {
      Forward();
    }
    if (number == 2) {
      Left();
    }
    if (number == 3) {
      Right();
    }
    if (number == 4) {
      Backward();
    }
    if (number == 5) {
      Servo_Centre();
    }
    if (number == 6) {
      Servo_Left();
    }
    if (number == 7) {
      Servo_Right();
    }
    if (number == 8) {
      led_on();
    }
    
   if (number == 9) {
    led_off();
   }
  }
}

// Callback for sending data
void sendData() {
  Wire.write(number);
}

void scan() //This function tells the robot to scan for obstacles  
{
  if (sweep_pos <=0) {
    pos_index = 20;
  }
  else if (sweep_pos >=180) {
    pos_index = -20;
  }
  Serial.print ("pos_index = ");
  Serial.println(pos_index);
  sweep_pos += pos_index;
  Serial.print("sweep_pos = ");
  Serial.println(sweep_pos);
  myservo.write (sweep_pos);
}

void Servo_Centre()
{
  Serial.println("");
  Serial.println("Scanning Forward");
  if (sweep_pos != 90) { 
  myservo.write(90);
  sweep_pos = 90;
  ping();
  }
}

void Servo_Right()
{
  Serial.println("");
  Serial.println("Scanning Right");
  if (sweep_pos != 20) {
    myservo.write(20);
    sweep_pos = 20;
    ping();
    Serial.println("Distance right = ");
    Serial.print(distance);
    Serial.print(" cm"); 
  }
}

void Servo_Left()
{
  Serial.println("");
  Serial.println("Scanning Left");
  if (sweep_pos != 160) {
    myservo.write(160);
    sweep_pos = 160;
    ping();
  }
}

void led_on()
{
  Serial.println("");
  Serial.println("Led On");
  digitalWrite(Led, HIGH); 
}

void led_off()
{
  Serial.println("");
  Serial.println("Led Off");
  digitalWrite(Led, LOW);
}

//Read the HC-SR04 sensor
unsigned long ping()
{
  //Trigger the sensor to send out a ping
  digitalWrite (trigPin, LOW);
  delayMicroseconds (5); 
  digitalWrite (trigPin, HIGH);
  delayMicroseconds (10);
  //Measure how long the ping took to return and convert to centimeters
  return (microsecondsToCentimeters (pulseIn (echoPin, HIGH)));
}

void Forward()                                    //This function tells the robot to go forward 
{
  Serial.println("");
  Serial.println("Moving forward");
  // turn on left motor   
  digitalWrite(in1, HIGH);   
  digitalWrite(in2, LOW);   
  // set speed out of possible range 0~255
  analogWrite(enA, 255);   
  // turn on right motor   
  digitalWrite(in3, LOW);   
  digitalWrite(in4, HIGH);   
  // set speed out of possible range 0~255   
  analogWrite(enB, 255);   
  //delay(100);
}

void Backward()                                  //This function tells the robot to move backward
{
  Serial.println("");
  Serial.println("Moving backward");
  // turn on left motor   
  digitalWrite(in1, LOW);   
  digitalWrite(in2, HIGH);   
  // set speed out of possible range 0~255
  analogWrite(enA, 255);   
  // turn on right motor   
  digitalWrite(in3, HIGH);   
  digitalWrite(in4, LOW);   
  // set speed out of possible range 0~255   
  analogWrite(enB, 255);   
  //delay(100);
}

void Left()                                      //This function tells the robot to turn left
{
  Serial.println("");
  Serial.println("Moving left");
  digitalWrite(in1, LOW);
  digitalWrite(in2, HIGH); 
// set speed out of possible range 0~255
  analogWrite(enA, 255); 
  digitalWrite(in3, LOW);
  digitalWrite(in4, HIGH);
// set speed out of possible range 0~255   
  analogWrite(enB, 255);   
  //delay(100);  
}

void Right()                                    //This function tells the robot to turn right
{
  Serial.println("");
  Serial.println("Moving right");
  digitalWrite(in1, HIGH);
  digitalWrite(in2, LOW);
// set speed out of possible range 0~255
  analogWrite(enA, 255); 
  digitalWrite(in3, HIGH); //Was High
  digitalWrite(in4, LOW);
  analogWrite(enB, 255);
  //delay(100);
}

void Stop()                                     //This function tells the robot to stop moving
{
  Serial.println("");
  Serial.println("Stopping");
// now turn off motors   
  digitalWrite(in1, LOW);   
  digitalWrite(in2, LOW);
  //analogWrite(enA, 0);  
  digitalWrite(in3, LOW);   
  digitalWrite(in4, LOW);
  //analogWrite(enB, 0);
}

Whenever you receive a character, assign the millis() time to a variable. In loop(), create a timing loop that checks to see whether the current time exceeds that by more than the desired interval. Use that information to control the mode.

I generally agree with what @aarg has said in Reply #1.

But even then it won't be simple because the normal intervals between useful messages could be mistaken for a communication failure.

You probably need code in your RPI that sends data at least every X seconds (perhaps data that is not meant to do anything) so that an lnterval longer than X (maybe 2X) means that the RPI has stopped altogether rather than that it is idling between useful messages. Then you could record millis() when each message arrives rather than each character.

...R

So declare a variable:

unsigned long data_interval;

And then in this section set Millis() to the data_interval

while(Wire.available()) {
    data_interval = millis()
    number = Wire.read();
    Serial.println("data received: ");
    Serial.println(number);

And in the main loop of the program add a timing loop that checks the var data_interval and if it exceeds x millis() run the autonomous function?

Sorry if its not syntax correct but i'll get there?

That’s the general idea.

But the code in the main part of the program should be like this

if (millis() - data_interval >= maxGap) {

and I suspect the name lastDataReceivedMillis might be more meaningful than data_interval

…,R

Think I may have solved this now - thanks.

When I switch on the RPi and the Robot / Nano and start the Python Master Program if I do not enter any commands on the web page control panel after 10 + 5 seconds the Servo starts to move on its own as it has switched to Autonomous mode within which the only command so far is to run the scan() function (which sweeps the servo), as soon as I interact it stops.

This is the loop section in which I placed the time test to switch to Autonomous mode:

void loop() {
  delay(1000); // Sets a short delay after switching on 
  ping();
  //scan(); 
  //get_battery(); 
  while(true)
  {
    if (Wire.available())  
    {
    continue;
    }
    else if(millis() - lastDataReceivedMillis >= maxGap) 
    {
      Serial.println("Switching to autonomous mode in 5 seconds");
      delay(5000);
      AutonomousMode();
    }
   
  }
}

The two variables "lastDataReceivedMillis" and "maxGap" were declared at the start of the program as unsigned long variables.

Don't mix delay() with millis(). Use millis() to manage all the timing because nothing can happen during a delay(). See several things at a time for an extended example

This should probably go in setup()

delay(1000); // Sets a short delay after switching on

...R

Robin2:
Don’t mix delay() with millis(). Use millis() to manage all the timing because nothing can happen during a delay(). See several things at a time for an extended example

This should probably go in setup()

delay(1000); // Sets a short delay after switching on

…R

Done - thanks

I have made a few more tweeks with loads more to implement but everything is working pretty well, still need to fine tune the Left and Right movement.