xBee Virtual USB question

I am trying to get a Pi and Arduino Mega to communicate via xBee.

Pi has xBee Explorer USB on it (ttyUSB0)
Arduino has xBee Arduino Shield on it

1)My code works if I plug the mega directly into the Pi and use ttyACM0 (as long as shield is removed from arduino)

  1. If I put my xBee Arduino shield on the Mega and switch my code to use ttyUSB0, the Pi receives blank lines.

  2. If take the xBee from the arduino shield and put it on another explorer USB on my computer, I can communicate directly with the Pi. So I know the Pi works.

This leads me to the following question:
What do I need to do to make the Mega communicate with the xBee? I have the shield switch set to UART. I also, on a whim, tried using an xBee Explorer Regulated and manually pinning it to the Mega with the same result.

I feel this may be related to the fact that serial by USB does not work with the shield plugged in. The Pi cannot communicate direct unless i remove the shield, I also cannot program the arduino without removing the shield.

Arduino has xBee Arduino Shield on it

Which shield? What code? How are the XBees configured?

Shield is standard Sparkfun xBee Arduino shield. I manually wired it to Serial3 instead of Serial, and it's working, albeit laggy,

Here is the Arduino code, followed by the Pi Code.

const byte numChars = 32;
char receivedChars[numChars];
char tempChars[numChars];        // temporary array for use when parsing

unsigned long previousMillis = 0;
long interval = 2000; 
unsigned long currentMillis = 0;

      // variables to hold the parsed data
int Joy1X = 0;
int Joy1Y = 0;
int Joy2X = 0;
int Joy2Y = 0;
int Joy3X = 0;
int Joy3Y = 0;
int Sw1 = 0;
int Sw2 = 0;
int Sw3 = 0;
int Sw4 = 0;

String sentCommand = "";
String call = "KF5RVR ";
String start = "{";
String last = "}";
String comma = ",";

boolean newData = false;
boolean firstContact = false;
boolean waitingForReply = false;

int RangeLF = 0;
int RangeRF = 0;
int RangeLR = 0;
int RangeRR = 0;
int AccelX = 0;
float AccelX_abs;
int AccelY = 0;
float AccelY_abs;
int AccelZ = 0;
float AccelZ_abs;
int roll = 0;
int pitch = 0;

// input string from Robot: {255,255,255,255,255,255,255}
// output string to robot: {100,100,100,100,100,100,1,1,1,1}

//========================================================================

void setup() {
    pinMode(13,OUTPUT);
    pinMode(A0,INPUT);
    pinMode(A1,INPUT);
    pinMode(A2,INPUT);
    pinMode(A3,INPUT);
    pinMode(A4,INPUT);
    pinMode(A5,INPUT);
    
    digitalWrite(13,LOW);
  
    Serial3.begin(4800);
    Serial.begin(9600);
    establishContact();
}

void loop() {
    currentMillis = millis();
    recvWithStartEndMarkers();
    
    if (newData == true) {
        
        //Serial.print("Received: ");
        //Serial.println(receivedChars);
        
        strcpy(tempChars, receivedChars);
            // this temporary copy is necessary to protect the original data
            //   because strtok() used in parseData() replaces the commas with \0
        parseData();
        showParsedData();
        newData = false;
        waitingForReply = false;
    } else{
        if ((Serial3.available() == 0) && (!waitingForReply)){
          transmitControlValues();
          waitingForReply = true;
        }
    }
    
    WarningLights();
}

//========================================================================

void establishContact() {
  while (Serial3.available() <= 0) {
    Serial3.println('A');   // send a capital A
    delay(1000);
  }
}

void recvWithStartEndMarkers() {
    static boolean recvInProgress = false;
    static byte ndx = 0;
    char startMarker = '{';
    char endMarker = '}';
    char rc;

    while (Serial3.available() > 0 && newData == false) {
        rc = Serial3.read();
        if (rc == 'C') {
//            //transmitControlValues();
//            //waitingForReply = true;
//            break;
            Serial3.flush();
            Serial3.println('A');
            break;
          }
//        previousMillis = currentMillis;

        if (firstContact == false){
           if (rc == 'B'){
                Serial3.flush();
                firstContact = true;
                                                    //Serial.println("First Contact Made");
           }
        } else {
          if (recvInProgress == true) {
              
              if (rc != endMarker) {
                  receivedChars[ndx] = rc;
                  ndx++;
                  if (ndx >= numChars) {
                      ndx = numChars - 1;
                  }
              }
              else {
                  receivedChars[ndx] = '\0'; // terminate the string
                  recvInProgress = false;
                  ndx = 0;
                  newData = true;
                  previousMillis = currentMillis;
              }
          } else if (rc == startMarker) {
              recvInProgress = true;
          }
        } 
    }
}

void parseData() {      // split the data into its parts

    char * strtokIndx; // this is used by strtok() as an index

    strtokIndx = strtok(tempChars,",");      // get the first part - the string
    RangeLF = atoi(strtokIndx);  // convert this part to an integer
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    RangeRF = atoi(strtokIndx);     // convert this part to an integer

    strtokIndx = strtok(NULL,",");      // this continues where the previous call left off
    RangeLR = atoi(strtokIndx);  // convert this part to an integer
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    RangeRR = atoi(strtokIndx);     // convert this part to an integer
        
    strtokIndx = strtok(NULL,",");     // this continues where the previous call left off
    AccelX = atoi(strtokIndx);  // convert this part to an integer
    AccelX_abs = ((float)AccelX / 1000) * 9.8;
 
    strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
    AccelY = atoi(strtokIndx);     // convert this part to an integer
    AccelY_abs = ((float)AccelY / 1000) * 9.8;

    strtokIndx = strtok(NULL,",");      // this continues where the previous call left off
    AccelZ = atoi(strtokIndx);  // convert this part to an integer
    AccelZ_abs = ((float)AccelZ / 1000) * 9.8;

    roll = atan2(AccelY_abs,AccelZ_abs) * 57.3;
    pitch = atan2( -1*AccelX_abs , sqrt((AccelY_abs*AccelY_abs)+(AccelZ_abs*AccelZ_abs))) * 57.3;
}

void showParsedData() {
    Serial.println("");
    Serial.println(currentMillis);
    Serial.print("RangeLF ");
    Serial.println(RangeLF);
    Serial.print("RangeRF ");
    Serial.println(RangeRF);
    Serial.print("RangeLR  ");
    Serial.println(RangeLR);
    Serial.print("RangeRR ");
    Serial.println(RangeRR);
    Serial.print("AccelX ");
    Serial.println(AccelX);
    Serial.print("AccelX_abs ");
    Serial.println(AccelX_abs);
    Serial.print("AccelY  ");
    Serial.println(AccelY);
    Serial.print("AccelY_abs  ");
    Serial.println(AccelY_abs);
    Serial.print("AccelZ ");
    Serial.println(AccelZ);
    Serial.print("AccelZ_abs ");
    Serial.println(AccelZ_abs);
    Serial.print("Roll  ");
    Serial.println(roll);
    Serial.print("Pitch ");
    Serial.println(pitch);
}

void transmitControlValues() {
    Joy1X = analogRead(A0);
    Joy1Y = analogRead(A1);
    Joy2X = analogRead(A2);
    Joy2Y = analogRead(A3);
    Joy3X = analogRead(A4);
    Joy3Y = analogRead(A5);
    Sw1 = 0;
    Sw2 = 1;
    Sw3 = 0;
    Sw4 = 1;
    

  /*sentCommand = '{' + Joy1X + ',' + Joy1Y + ',';
    sentCommand += Joy2X + ',' + Joy2Y + ',';
    sentCommand += Joy3X + ',' + Joy3Y + ',';
    sentCommand += Sw1 + ',' + Sw2 + ',';
    sentCommand += Sw3 + ',' + Sw4 + '}';*/

    sentCommand = call + start + Joy1X + comma + Joy1Y + comma;
    sentCommand += Joy2X + comma + Joy2Y + comma;
    sentCommand += Joy3X + comma + Joy3Y + comma;
    sentCommand += Sw1 + comma + Sw2 + comma;
    sentCommand += Sw3 + comma + Sw4 + last;

                                                              //Serial.println("Transmitting:");
    Serial3.println(sentCommand);
                                                              //delay(5000);
    
}

void WarningLights(){
    
    if(currentMillis - previousMillis > interval){
      // Illuminated when linked
      digitalWrite(13, LOW);
    } else {
      // turned off when comms lost
      digitalWrite(13, HIGH);
    }

    if (AccelX > 300){
      //X angle greater than 30% (2.9 m/s2)
      digitalWrite(25,HIGH);
    } else {
      digitalWrite(25,LOW);
    }

    if (AccelY > 300){
      //Y angle greater than 30% (2.9 m/s2)
      digitalWrite(26,HIGH);
    } else {
      digitalWrite(26,LOW);
    }

    if (AccelZ < 900){
      //Z angle less than 90% (8.82 m/s2)
      digitalWrite(27,HIGH);
    } else {
      digitalWrite(27,LOW);
    }
  
}

Here is the Python3 code from the Pi.

It seems the comms are averaging about 6 tenths of a second to parse completely, when unregulated- it tends to push in batches of 3 comms at a time. My math says that at 4800 bps, it should be taking less than 3 tenths of a second. As a result, I have set the program to pause for half a second simply to make the speed of comms more predictable.

Should I bump the rate up to 9600? The plan is to have the xBee's up to 1000 feet apart, and they are 63mw series 2 Pro's.

Video of the transmission behavior:

import serial
from time import sleep
import time
import board
import busio
import adafruit_lsm9ds1
import Adafruit_ADS1x15
import math

#port = serial.Serial("/dev/ttyAMA0", baudrate=9600, timeout=3.0)
#ser = serial.Serial("/dev/ttyACM0", baudrate=4800, timeout=3.0) #USB Serial to Arduino
ser = serial.Serial("/dev/ttyUSB0", baudrate=4800, timeout=3.0) #USB xBee

# I2C connection:
i2c = busio.I2C(board.SCL, board.SDA)
sensor = adafruit_lsm9ds1.LSM9DS1_I2C(i2c)
adc48 = Adafruit_ADS1x15.ADS1115()
adc49 = Adafruit_ADS1x15.ADS1015(address=0x49, busnum=1)
GAIN = 1

incomingCommand = ""
prevPosInString = 0
nextPosInString = 0
joy1X = 0
joy1Y = 0
joy2x = 0
joy2Y = 0
joy3X = 0
joy3Y = 0
sw1 = 0
sw2 = 0
sw3 = 0
sw4 = 0

waitingForContact = 1
sentCommand=""
rangeLF = 0
rangeRF = 0
rangeLR = 0
rangeRR = 0
accelX = 0
accelY = 0
accelZ = 0

speedFactor = 1.0
crabSteer = 0


def driveMotors():
    if (sw1 == "1"):
        speedFactor = 0.25
        # set forward ESC to 25% of received Joy1Y
    else:
        speedFactor = 1.0
        # set forward ESC to received Joy1Y
    
    if (sw2 == "1"):
        crabSteer = 1
        # set front and rear steer to Joy1X
        # this makes all 4 tires point the same direction
    else:
        crabSteer = 0
        # set front steer servo to Joy1X
        # set rear steer servo to -1*Joy1X
        # this makes tires turn about a single point
    
    
    
def readRangeSensors():
    global rangeLF, rangeRF, rangeLR, rangeRR
    values48 = [0]*4
    values49 = [0]*4
    for i in range(4):
        values48[i] = adc48.read_adc(i, gain=GAIN)
        values49[i] = adc49.read_adc(i, gain=GAIN)
    rangeLF = values48[0]
    rangeRF = values48[1]
    rangeLR = values48[2]
    rangeRR = values48[3]


def readAccelerometer():
    global accelX, accelY, accelZ
    accelX, accelY, accelZ = sensor.acceleration
    accelX = round((accelX / 9.8) * 1000)
    accelY = round((accelY / 9.8) * 1000)
    accelZ = round((accelZ / 9.8) * 1000)
    magX, magY, magZ = sensor.magnetic
    gyroX, gyroY, gyroZ = sensor.gyro
    temp = sensor.temperature

while waitingForContact:
    x = ser.read()
    print(x)
    if ( x.decode() == 'A' ):
            ser.write(b'B')
            print('B')
            waitingForContact = 0
    else:
        ser.write(b'C')
        print('C')
        sleep(0.500)

while True:
    
    driveMotors()
    readRangeSensors()
    readAccelerometer()
    
    if(ser.inWaiting() == 0):
        sleep(0.500) #4800 bps = 600 bytes per second @ 42 bytes per comm = 0.071 per comm
    
    while(ser.inWaiting() > 0):
        sleep(0.002) #4800 bps = 600 bytes per second @ 42 bytes per comm = 0.071 per comm
        incomingCommand = str(ser.readline())
        print("in: " + incomingCommand[0:-2])
        
#        print incomingCommand.find("A")
        
        if(incomingCommand.find("A") > -1):
            ser.write(b'B')
            print('B')
            ser.flush()
            sleep(0.250)
#            incomingCommand = str(ser.readline())
        
#        incomingCommand = incomingCommand[1:-3]
#        print("revised1: " + incomingCommand)

        if(len(incomingCommand) > 15):
        
            prevPosInString = 0
            
            nextPosInString = incomingCommand.find("{")
            incomingCommand = incomingCommand[nextPosInString+1:-6]
            prevPosInString = nextPosInString
            print("revised: " + incomingCommand)
        
            nextPosInString = incomingCommand.find(",")
    #        print "index1: %s" % (nextPosInString,)
            joy1X = incomingCommand[prevPosInString:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index2: %s" % (nextPosInString,)
            joy1Y = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index3: %s" % (nextPosInString,)
            joy2X = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index4: %s" % (nextPosInString,)
            joy2Y = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index5: %s" % (nextPosInString,)
            joy3X = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index6: %s" % (nextPosInString,)
            joy3Y = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index7: %s" % (nextPosInString,)
            sw1 = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index8: %s" % (nextPosInString,)
            sw2= incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index9: %s" % (nextPosInString,)
            sw3 = incomingCommand[prevPosInString+1:nextPosInString]
            prevPosInString = nextPosInString
            
            nextPosInString = incomingCommand.find(",",prevPosInString+1)
    #        print "index10: %s" % (nextPosInString,)
            sw4 = incomingCommand[prevPosInString+1:]
            prevPosInString = nextPosInString
            
    #        print(joy1X)
    #        print(joy1Y)
    #        print(joy2X)
    #        print(joy2Y)
    #        print(joy3X)
    #        print(joy3Y)
    #        print(sw1)
    #        print(sw2)
    #        print(sw3)
    #        print(sw4)

        if (ser.inWaiting() == 0):
            sentCommand = "KF5RVR {" + str(rangeLF) + "," + str(rangeRF) + "," + str(rangeLR) + "," + str(rangeRR) + "," + str(accelX) + "," + str(accelY) + "," + str(accelZ) + "}"
            sentCommand = sentCommand
            ser.write(sentCommand.encode())
            print("out: " + sentCommand)
            print("")
            
        # End Serial Comms
    # End While True
# Start Rest Of Code Here
    
    
ser.close()

Should I bump the rate up to 9600?

Heavens, no. You SHOULD bump the rate to 115200.

            Serial3.flush();
            Serial3.println('A');

You don't have a clue what flush() does, do you?

PaulS:
Heavens, no. You SHOULD bump the rate to 115200.

            Serial3.flush();

Serial3.println('A');



You don't have a clue what flush() does, do you?

The xBee's are set up at 4800 right now, so I set UART to match. I haven't done a range test yet, but I anticipate that at some point I will hit a wall of range and bitrate. At 2.4 GHz and a range of 1,000 ft (300 metres) with 6dbi antennas and 63 mW, I want to maximize the three-retries offered by the xBee firmware while making the first try as efficient as possible.

The section you quoted is specifically an "oh sh**" safety catch. If one or the other devices is rebooted, I have that in there to re-synchronize communication and flush out any partially received strings as well as the scraps of re-sync lettering.

The is idea is this:

Initial handshake:
Controller sends an 'A' once every second
Robot listens for the 'A'. Once received, it sends the controller a 'B' as an ack and to start the data transfer process.

If the robot is rebooted:
The controller is waiting for a string of params, but the robot is also listening for the 'A'. So the robot sends a 'C', which tells the controller that the robot was rebooted and that data transfer needs to be synced by sending an 'A', which then has the robot send a 'B'. Basically commanding a new initial handshake above. I just flush out the serial buffer prior to performing the handshake.

If the controller is rebooted:
The controller is now sending an 'A'. The robot is waiting for a string of params. The robot then detects the 'A', sends a 'B' back. Again, commanding a new initial handshake.

It's not pretty, but it's the best implementation of handshake and flow control that I could think of without going way outside my comfort zone.

Doing a distance check, right now i'm at 35 feet indoors through 2 walls, and I'm starting to get some incomplete strings with UART and xBee both at 4800 bps. I was hoping i'd get a little further out of 63mW and 6dbi on both ends. I don't have a good way to do an outdoor test at the moment.

The xBee's are set up at 4800 right now

Why? They are capable of far faster than that.

I was hoping i'd get a little further out of 63mW and 6dbi on both ends.

You've not mentioned what kind of antennas you are using. I get far better range than that, with the small (1") wire antennas.

I just flush out the serial buffer prior to performing the handshake.

The flush() function blocks until the outgoing serial buffer is empty. Doing that BEFORE sending anything, and then not waiting for it to be sent, seems pointless.

PaulS:
Why? They are capable of far faster than that.
You've not mentioned what kind of antennas you are using. I get far better range than that, with the small (1") wire antennas.
The flush() function blocks until the outgoing serial buffer is empty. Doing that BEFORE sending anything, and then not waiting for it to be sent, seems pointless.

This is my first time with xBee, so I am slowly learning. Perhaps you are correct, and I haven't learned enough. Please, do teach me.

The antennas are these: https://www.amazon.com/gp/product/B01KBU61S8/

I thought Flush() only cleared the incoming buffer. Am I wrong on that? What should I be doing differently?

The antennas are these: https://www.amazon.com/gp/product/B01KBU61S8/

How are those connected to the XBees? Which XBees do you have?

I thought Flush() only cleared the incoming buffer. Am I wrong on that?

Yes. The flush() function used to dump all unread data from the incoming buffer, but that was changed years ago. Now, it forces the data in the outgoing buffer to be sent, before the Arduino does anything else.

You would now empty the incoming serial buffer with a while loop:

while(Serial.available() > 0)
{
   Serial.read(); // No need to save the value...
}

But, why would you want to dump random amounts of unread data?

Antennas are RPSMA to match the xBees

So the string sent from the controller looks like this:

KF5RVR {000,000,000,000,000,000,0,0,0,0}

to represent {Joy1X, Joy1Y,Joy2X,Joy2Y,Joy3X,Joy3Y,sw1,sw2,sw,sw3,sw4}

However during the resync process, the Pi was lagging. So I would get

AAAAAAAAAAKF5RVR {000,000,000,000,000,000,0,0,0,0}
B
AAA
B
K
B
F
B
5
B
R
B
V
B
R
B

B
{000,000,000,000,000,000,0,0,0,0}

Once I added the flush command, it cleared all that up.

I used XCTU to change the xBee's to 19200 and changed the code on both ends to match.

This resulted in giberish that was erroring out the code.

I backed both xBees off to 9600 and matched the code, and it's working right now in close proximity, I'll have to do another range check across the house to see if it holds.