(SOLVED) Issues with Ack data with the NRF24 (Library RF24.h)

I've been dealing with an issue with my project for more than a month now, and figured some background information on ack data might help.

I have a Remote Control Unit (RCU), connecting to a Receiving Unit (RU) via two NRF24's. It sends data fine (in the form of an array of 10 ints), but when it comes to get the ints back, it sometimes does and sometimes doesn't receive the ack payload

Since it's pretty consistent on when it does and doesn't, I'm certain it's a coding error, and won't bother asking you to solve it for me. Rather than that, I'll just ask a few questions on how Ack data works. Namely,

  • I gather from Robin2's post that the ack data can be a maximum of 32 bytes long, and as ints are 2 - 4 bytes, that means an array of ints could only hold 8, and still be sent back with ack.
    Is this correct? (my returnArray only has 2 ints in it, so that's not the issue)

  • In the below code (to pre-load the array, ready for sending back once it receives a signal from the master), what does the '1' represent?

radio.writeAckPayload(1,&returnArray,sizeof(returnArray));

  • What happens if you pre-load the ack payload, then attempt to preload it again before its sent? Does it override the first payload, stack it somehow, or glitch it out?

  • What common errors crop up when using ack payloads?

Edit

Problem now resolved. Posting some example scripts of ack payloads in action, if anyone wants them.

Also, using the line

radio.writeAckPayload(1,&returnArray,sizeof(returnArray));

more than once before it's actually sent, just overrides the existing data.

05-AckPayloadMaster.ino (2 KB)

05-AckPayloadSlave.ino (3.07 KB)

1 Like

Without seeing your pair of programs I can only generalize.

On an Uno an int is 2 bytes long so you could send 16 of them in a single nRF24 message.

...R
Simple nRF24L01+ Tutorial

Oh, that helps.

I'll post the relevant code below. Since one weighs in at about 1,100 lines of code, and the other at around half that, I'll cut out the bits that arn't used while this is running.

*Edit
Just posting all this made me realise what a massive undertaking it is to go through it all, especially with someone unfamiliar with the code. I'd completely understand if you're not willing to do it.

  • WHATS SUPPOSED TO HAPPEN -
  • The RCU sends a signal to the receiving unit to activate a remote program. The program itself is housed on the receiving unit, and consists (at the moment) of 4 command lines of ints. These are interpreted to tell the Receiving Unit what component to activate (Ie, Left Motor), in what direction, at what speed, and for how long.

All that works fine.

  • ISSUES
    When each command line is complete, it's supposed to send back a signal to the RCU stating so. When all of them are complete, it sends a different signal.

It sends the 'command line complete' signal the first two times, but not after that. It doesn't send the 'program complete' signal at all.

activating the program subsequent times results in no 'command line complete' signals coming back at all, unless the receiving unit is first turned off and on.

  • BUG TESTING
    I changed the 'command line complete' signal 'returnArray[0] = 1' to the 'program complete' signal ('returnArray[0] = 2' to test that the RCU was actually picking it up fine. Worked fine. Changed it back.

-OTHER ISSUES
There is other issues with the functionality here, which I include only because you may notice them going through the code. They should be fairly easy to fix,

  • On the RCU, the 'command line complete' message only flashes for 1/10 of a second, rather than staying up long enough to read.

  • The RCU has a 'cancel program' command, that sends a signal to the Receiving Unit to cancel the running program. Used to partially work. Now doesn't. Not fussed, I'll fix it later.

  • Line 229, where it reads

      else if (commandLine == 5) // is no commandline 5. So ends it.
        {
  
        Serial.println("End of Program");
        programRunning = false;
//        returnArray[0] = 2;
 //       radio.writeAckPayload(1,&returnArray,sizeof(returnArray));  // this and previous line tell RCU program is finished
      
        leftMotor->run(RELEASE);
       rightMotor->run(RELEASE);
        }

never activates. or at least, the 'end of program' message never comes up. This is ok though, as it's replicated by the code almost directly below it. (below)

  if (ExpiredTime(v1Time,v1StartT) == true) 
    {
      commandRunning = false;
      leftMotor->run(RELEASE);
      rightMotor->run(RELEASE);

    if (commandLine != 5)
      {
        returnArray[0] = 1;
        radio.writeAckPayload(1,&returnArray,sizeof(returnArray));      
      }

     if (commandLine == 5) // is no commandline 5. So ends it.
      {
         Serial.println("End of Program 2");
         programRunning = false;
         commandRunning = false;
         returnArray[0] = 2;
         radio.writeAckPayload(1,&returnArray,sizeof(returnArray));        // this and previous line tell RCU program is finished
        
         leftMotor->run(RELEASE);
         rightMotor->run(RELEASE);
       }

the 'end of program 2' message always comes up.

(Will post code in next post, to promote readability)

Ok, first the Receiving Unit code. I had to snip it a bit, since it exceeds the maximum post length by a lot

*/
// ++++++++++++ PRE-SETUP ++++++++++++++++

// ============= COMMAND AND CONTROL =============

int commandLineArray[10];



unsigned long v1StartT = millis; // records what time the motors start
int v1Time = 10;                // length of time for the motor to run (in 1/10 of a second)
int commandLine = 1;

bool commandRunning = false;
bool programRunning = false;

// ================== MOTORSHIELD ================


#include <Wire.h>
#include <Adafruit_MotorShield.h>

// Servos
#include <Servo.h>
Servo rudder;
Servo elevator;

int rudderPos = 0;
int elevatorPos = 0;


// Create the motor shield object with the default I2C address
//Adafruit_MotorShield AFMS = Adafruit_MotorShield(); 
// Or, create it with a different I2C address (say for stacking)
Adafruit_MotorShield board1 = Adafruit_MotorShield(0x61); 
Adafruit_MotorShield board2 = Adafruit_MotorShield(0x60); 

 
// Select which 'port' M1, M2, M3 or M4. In this case, M1
Adafruit_DCMotor *leftMotor = board1.getMotor(1);
Adafruit_DCMotor *rightMotor = board2.getMotor(1);

// You can also make another motor on port M2
//Adafruit_DCMotor *myOtherMotor = AFMS.getMotor(2);



// ===================== WIRELESS ====================

#include <SPI.h>
#include <RF24.h>


const byte thisSlaveAddress[5] = {'R','x','A','A','A'};

RF24 radio(9,10); // first is pin number for CE pin, 2nd is for CSN pin

int receivedArray[10] ; // this must match dataToSend in the TX. name of array to be sent

int returnArray[2]; // not currently used - plann to use for future iterations (sending sensor data)
bool returnBool = false; // Hope to use this for sending 'end of current command line' signal.

bool newData = false; // used to print new data if true.


// =============== BUZZER =======================

int buzzer = 2;// set the buzzer control digital IO pin to 2
unsigned long buzzerStartT = 0; // records the start of the buzzer time
bool buzzerSwitch = false; // determines iof
unsigned long buzzerShortT = 0;
bool buzzerOnOff = LOW;

int buzzerLength = 0; // buzzer length in seconds. 
int buzzerFreq = 0;// (1-20 range)



// ++++++++++++++++++ SETUP +++++++++++++++++++++
void setup() {

// ========================= MOTORSHIELD ==============================  
  (not relevant)


// ===================== WIRELESS ====================


 radio.begin();
//    radio.setDataRate( RF24_250KBPS );
//    radio.openReadingPipe(1, thisSlaveAddress);
//    radio.startListening();
 radio.openReadingPipe(1, thisSlaveAddress);
 radio.startListening();
 radio.enableAckPayload();
 radio.enableDynamicPayloads();
 Serial.println("Radio Initialised");

    // Modifications to increase reliability
 radio.setDataRate( RF24_250KBPS );
 //radio.setPALevel (RF24_PA_MIN);
 radio.setRetries (10,15);

// =============== BUZZER =======================


    // function to make buzzer buzz for X seconds at Y  frequency. Normally  you'd drop these
      // in after conditions
    buzzerSwitch = true;
    buzzerStartT = millis(); // sets what mission time the buzzer started at
    buzzerLength = 1; // length in 1/10th of a second
    buzzerFreq = 1;   //between 1 and 20

    Serial.println("Buzzer initialised");
//    buzzerShortT = millis(); // used for frequency

}

// ++++++++++++++++++++++++ LOOP ++++++++++++++++++++++++



void loop() 
{

   if (newData == true)
   {
    if (receivedArray[0] == 5)
    {
      realTimeControl();    
    }
  } 



// first activation of remote program.
  if (receivedArray[0] == 6)  
  {
    Serial.println("Program activation signal received from wireless");
    // change 'remote program activated' to true.
    commandLine = 1; 
    programRunning = true;
    
    receivedArray[0] = 0; // this command, in combination with one in the RCU (submenu 420) prevents the program being exectuted with every iteration fo the loop
  }


if (programRunning == true)
  {
    //Serial.println("Loop");
  // checks if it's received a cancel command from RCU. (can also use same command at end of program)
  if (receivedArray[1] == 404)
    {


      // change a vairable so it exits program mode
      Serial.println("Cancel Command Received");
      programRunning = false;
      
      leftMotor->run(RELEASE);
      rightMotor->run(RELEASE);
    }
    
  
  
  
  if ((commandRunning == false)&&(programRunning == true))  // if ready for next command line, loads it.
    {
//          Serial.println("2nd step");
      if (commandLine == 1)
        {
          ComLine1();
          RunCommandLine();
  
        }
      else if (commandLine == 2)
        {
          ComLine2();
          RunCommandLine();
        }
      else if (commandLine == 3)
        {
          ComLine3();
          RunCommandLine();
        }
      else if (commandLine == 4)
        {
  
          ComLine4();
          RunCommandLine();
        }
      else if (commandLine == 5) // is no commandline 5. So ends it.
        {
  
        Serial.println("End of Program");
        programRunning = false;
//        returnArray[0] = 2;
 //       radio.writeAckPayload(1,&returnArray,sizeof(returnArray));  // this and previous line tell RCU program is finished
      
        leftMotor->run(RELEASE);
       rightMotor->run(RELEASE);
        }        
    }
  
  
  
  
  // if set time has expired, sets it ready to go onto the next command
  // also resets all instruemnts to zero
  
  
  if (ExpiredTime(v1Time,v1StartT) == true) 
    {
      commandRunning = false;
      leftMotor->run(RELEASE);
      rightMotor->run(RELEASE);

    if (commandLine != 5)
      {
        returnArray[0] = 1;
        radio.writeAckPayload(1,&returnArray,sizeof(returnArray));      
      }

     if (commandLine == 5) // is no commandline 5. So ends it.
      {
         Serial.println("End of Program 2");
         programRunning = false;
         commandRunning = false;
         returnArray[0] = 2;
         radio.writeAckPayload(1,&returnArray,sizeof(returnArray));        // this and previous line tell RCU program is finished
        
         leftMotor->run(RELEASE);
         rightMotor->run(RELEASE);
       } 
      

      
      //noTone (2);
      // tone(2, (commandLineArray[1]*100));
      //leftMotor->run(BRAKE);
      //rightMotor->run(BRAKE);    
      //receivedArray[1] = 404;
    }
  }
// ========== MOTORSHIELD ===============

 // none

   // *** WIRELESS ***


//if (newData == true)      // THIS IS A DUMMY LINE TO PREVENT WIRELESS FROM ACTIVATING. DE_COMMENT BELOW LINE, REMOVE THIS
                          // TO REACTIVATE
   if (radio.available())
   {
    //Serial.println("Data Available");
    radio.read(&receivedArray, sizeof(receivedArray));
    newData = true ;

    returnArrayReset(); // resets all returnedArray values to zero
    radio.writeAckPayload(1,&returnArray,sizeof(returnArray)); // make sure the payload is updated to reflect this
   }

   else
   {
    newData = false;
   }


// =============== BUZZER =======================

  
}

Functions in next post. Will also edit in a bit, to highlight important parts

Included the functions I think are relevant. Left out functions for realtime control, as this function is not called while the remote program is activated.

// very simple for this one. 
// first int refers to what's controlled.
//    0 is nothing. 1 is buzzer. 2 is right. 3 is left. 4 is both.
// second int refers to value for it. IE, speed for motor, or tone for buzzer. 
// third int is a second value. Ie, 'Back', 'Forward', 'brake', or 'on/off' for the buzzer.
// fourth is a time limit, in 10ths of a second.
// fifth is a binary switch - for something that doesn't need a constant 'on' every loop.


void ComLine1()
  {
    // Forward
    commandLine = commandLine + 1; 
    commandRunning = true;
    v1StartT = millis();

    Serial.println("Command Line 1");


    commandLineArray[0] = 1;
    commandLineArray[1] = 10;
    commandLineArray[2] = 1;
    commandLineArray[3] = 10;  
    commandLineArray[4] = 1;  
      
  }

Functions ComLine 2 - 4 match this one.

  bool ExpiredTime (int _length, unsigned long  _startTime)
    {

      if ((millis()-_startTime) <=(_length*100))
        {
          //Serial.print(_startTime);
          //Serial.print(", ");
          //Serial.println(_length * 100);
          return false;
        }
      else 
        {
          return true;
        }
    }

  void returnArrayReset()
    {
      for (int i = 0; i <= 9; i++) 
      {
        returnArray[i] = 0;
      }
    }

  void RunCommandLine()
  {

    v1Time = commandLineArray[3];  

    if (commandLineArray[0] == 1)
      {
        Buzzer();
      }

    else if (commandLineArray[0] == 2) // left
      {
        Left();
      }

        else if (commandLineArray[0] == 3) // right
      {
        Right();
      }

    else if (commandLineArray[0] == 4) // both
      {
        Both();
      }

    
    
  }

 void Buzzer()
    {
     if (commandLineArray[4] == 1)
      { 
      tone (2, (commandLineArray[1]*100), (commandLineArray[3]*100));
      commandLineArray[4] = 0;

      } 
    }



void Left()
  {

//      leftMotor->setSpeed (255);
    leftMotor->setSpeed (commandLineArray[1]);
    if (commandLineArray[2] == 1)
      {
        leftMotor->run(FORWARD);
      }
    else if (commandLineArray[2] == 2)
      {
        leftMotor->run(BACKWARD);
      }
    leftMotor->run(BRAKE);
  }

void Right()
  {

  //  rightMotor->setSpeed (255);
  rightMotor->setSpeed (commandLineArray[1]);
    if (commandLineArray[2] == 1)
      {
        rightMotor->run(FORWARD);
      }
    else if (commandLineArray[2] == 2)
      {
        rightMotor->run(BACKWARD);
      }
    rightMotor->run(BRAKE);
  }

  void Both()
  {
    leftMotor->setSpeed (commandLineArray[1]);
    rightMotor->setSpeed (commandLineArray[1]);
    if (commandLineArray[2] == 1)
      {
        leftMotor->run(BACKWARD);
        rightMotor->run(BACKWARD);
      }
    else if (commandLineArray[2] == 2)
      {
        leftMotor->run(FORWARD);
        rightMotor->run(FORWARD);
      }
    rightMotor->run(BRAKE);
    leftMotor->run(BRAKE);
  }

In this post, snippets from the Remote Control Unit. Original runs to 1149 lines, so I won't be posting much of that.

Pretty sure it's not a problem with the RCU, but including it for the sake of completion.

Pre-setup

//WIRELESS
#include <SPI.h>
#include <RF24.h>



const byte thisSlaveAddress[5] = {'R','x','A','A','A'};


RF24 radio(9,10); // first is pin number for CE pin, 2nd is for CSN pin

//int sendArray[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10} ; // this must match dataToSend in the TX. name of array to be sent
                        // used to send data

 int sendArray[10]; // Hopefully creates an array full of zero's.

int receivedArray[2];    // used to store data received.   

bool rslt = false; // used to store success or fail of wireless transmission. 
                // Global, because is used in menu too.
bool sent = false; // Same as above, but used to mark when a transmission has been sent.                

//for time delay on wireless
unsigned long prevMillisW = 0;
int intervalMillisW = 1000;




// MENU
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Delay ints
unsigned long prevMillis = 0;
int intervalMillis = 0;


int sentTrue = 0;
int sentFalse = 0; // these two are used in the wireless conenction, to count how many
                      // missed and received pings it's god



// used to see what level the menu's at.
int Menu = 100;

bool newLoop = true; // used to make the program write some things only the first time they're created

Setup

void setup()
{

 
 lcd.begin(20, 4);              // start the library

 radio.begin();                 // starts radio
 radio.openWritingPipe(thisSlaveAddress);
 bool newData = false; // used to print new data if true.

 // Modifications to increase reliability
 radio.setDataRate( RF24_250KBPS );
 //radio.setPALevel (RF24_PA_MAX);
 radio.setRetries (10,15);

 radio.enableAckPayload();
 radio.enableDynamicPayloads();
 
 
 Serial.begin(9600); // for debugging purposes only


}

Loop

void loop()
{

  // below just for bug-testing the ack program
  if (receivedArray[0] != 0)
    {
      Serial.println(receivedArray[0]);
    }


  // LOADS MENU SCRIPT, IF PREVIOUS LOOP DIDN'T ASN'T FOR DELAY
  if ((millis() - prevMillis) >= intervalMillis)
  {
    menu();
  }

  // LOADS WIRELESS SCRIPT, ONCE PER SECOND. (THIS MAY END UP BEING SUBJECT TO CHANGE)
    if ((millis() - prevMillisW) >= intervalMillisW)
  {
    wireless();
    prevMillisW = millis();
  }

   if (radio.isAckPayloadAvailable())
    {
      radio.read(&receivedArray, sizeof(receivedArray));
    }


}

possibly relevant Functions

void wireless()
{
  rslt = radio.write(&sendArray, sizeof(sendArray));
  sent = true;
}

void firstRun(String _tempText, String _tempText2, String _tempText3, String _tempText4) // first time a menu runs, prints the text. This one is for menu  - single line, no printed variables.
 {
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(_tempText);
  lcd.setCursor(0,1);
  lcd.print(_tempText2);
  lcd.setCursor(0,2);
  lcd.print(_tempText3);
  lcd.setCursor(0,3);
  lcd.print(_tempText4);
  lcd_key = 5;  // sets the 'key pressed' to 'none', so it doesnt set off other keys
                // for this cycle
  newLoop = false;
  //delay(400);
 }





 int arrowKey(int _menu) // short code for navigating to new menu location
  {
     int newMenu = _menu;
     newLoop = true;
     //delay (500);  // this delay is needed so you don't cycle through every menu option 
                  // going down or up.

    // experimental code below, to replay the 'delay'. Tested. Works. 
    // Global variables can be modified in functions.
    prevMillis = millis();
    intervalMillis = 500;


                  
     return newMenu;
  }

Menu function snippets - First part, as well as part of menu that deals with the remote program.
Most relevant part is at the end - everything where Menu == 420.

int menu()
  {
  Thrust = analogRead(slidingSwitch);
  
  
  Thrust = map(Thrust, 0, 1024, 0, 9);
  
  

  prevMillis = millis();
  intervalMillis = 10;

  
  
  // read the buttons
  adc_key_in = analogRead(keyPad);      // read the value from the sensor 
  
   if (adc_key_in > 1000) 
     {
      lcd_key = 5;        // None
     } 
   if (adc_key_in < 750)
       {
        lcd_key = 4;      // Select
       }  
   if (adc_key_in < 520)
      {
      lcd_key = 0;        // Right
      } 
   if (adc_key_in < 350)
      {
      lcd_key = 2;        // Down
      }  
   if (adc_key_in < 200)
     {
      lcd_key = 1;        // Up
      } 
   if (adc_key_in < 100) 
     {
      lcd_key = 3;        // Left
     }  
  
  
  // 0 = Right  1 = Up   2 = Down   3 = Left    4 = Select    5 = None
  
  
  
  
  
  
  
        // Root menu 0 - Callibrate
  
    if (Menu == 100)           
      {
        if (newLoop == true) // if this is the first loop in here
          {
           firstRun("Wireless Connection", "Status", "", "");
          }
        if (lcd_key == 2)
          {
            // what to do if button is Down
            Menu = arrowKey(200);
          }
        if (lcd_key == 0)
          {
            // what to do if button is Right
            Menu = arrowKey(110); 
          }                                
     
       
      }




(Menu 110 to 320 cut)

   if (Menu == 410) // Choose a remote program to activate
      {
        if (newLoop == true)
          {
            firstRun("Pre-Programmed 1 N/A", "Pre-Programmed 2 N/A", "Pre-Programmed 3 N/A", "Self-Programmed  N/A");
            lcd.setCursor(0,0);
            lcd.blink(); 
            sendArray[0] = 0; // resets to zero, in case it misses it earlier
            sendArray[1] = 0;
          
          }

          prevMillis = millis();
          intervalMillis = 200;
          sent = false;          
          

(cut 'if press left, up or right, down' if statements)

            if (lcd_key == 4) // select
              {

                sendArray[0] = 6; // tells it ot execture program
                sendArray[1] = 1; // tells it which program to execute (the first)
                
                Menu = arrowKey(420);
                Wait2 = ((Ten * 10) + Digit);
                Serial.println(Wait2);
              }
      } 

(cut menus 411, 412, 413)

 if (Menu == 420) // Choose a remote program to activate
      {

        if (sent == true)
          {
              sendArray[0] = 0; // This line prevents the RU from trying to re-activate the same program every time 
                              // it receives the wireless array.
          }
        
        if (newLoop == true)
          {
            firstRun("Program Activated", "Press '<' to cancel", "or wait till end", "");
            lcd.setCursor(0,0);
            lcd.noBlink();
            intervalMillisW = 100;

          }

        if (receivedArray[0] == 1) // If it's received a 'commandline complete' signal from the car
          {
            lcd.setCursor(0,3);
            lcd.print("Line Fin - Next Line");
            
          }

        if (receivedArray[0] == 0) // resets line
          {
            lcd.setCursor(0,3);
            lcd.print("                    ");  
          }

        if (receivedArray[0] == 2) // Command Line has Ended
          {
            Menu = arrowKey(410);
            lcd.setCursor(0,3);
            lcd.print("Should Have Ended   ");
            intervalMillisW = 1000; 
          }   
          
              if (lcd_key == 3)// left
          {
            Menu = arrowKey(410);
            sendArray[1] = 404;
            intervalMillisW = 1000;
            
          }
      }

Please just post the complete programs in one piece for each. If a program is too long then add the .ino file as an attachment. However I would much prefer to see a short program that is complete and which illustrates the problem.

I'm not going to try to patch pieces of code together as I will probably introduce more errors.

...R

Ok, complete program posted. I've cut out the bits of code that arn't accessed. Tested to make sure it still works.

Will also upload some pics of the RCU and RU in question, if I can connect with my phone.

06-CutDownMaster.ino (15.5 KB)

09-CutDownSlave.ino (14.8 KB)

Car.jpg

RCU.jpg

When I suggested a short program I had in mind 20 or 30 lines of code. How could your program grow so big without resolving this problem?

I always do my testing of new techniques with very short programs.

IMHO you should only call writeAckPayload() immediately after a message has been received - in other words, only once for every received message.

...R

IMHO you should only call writeAckPayload() immediately after a message has been received - in other words, only once for every received message.

Thanks, I'll try this.

I always do my testing of new techniques with very short programs.

I'll try this too, see if I can narrow down what the problem is.

How could your program grow so big without resolving this problem?

I've been developing this program on and off for the last six months, and the ack payload is only the latest thing to be incorporated. (possibly because it's the first where I didn't develop it separately like you suggested)

Pretty much every step of the way has been filled with problems, but this is one of the few I've actually needed help with. Others include getting the Adafruit motorshield to work, getting the LCD panel to work, learning some physics based maths to determine what speed and torque I can get with my motors, which have a set RM, and various sized wheels, learning the importance of accuracy when marking out sheetmetal to cut & drill...

It's a fun project, cause it's constant improvement in a wide swath of areas, but it's a bit frustrating. It seems I have to fight for every step of progress, no matter how incrumental.

Planned expansions to the program once (if) I resolve the ack payload problem include restructuring the way the command program is loaded to make it more versatile, including adding addition end-triggers besides time. Also, either connecting a second arduino to the receiving unit, or learning how to connect multiple devices to the A4, A5 pins - which would allow me to connect a micro-sd card reader (to store the programs), and a compass. I originally planned to include a clock, before learning about the millis function. Having a log file with accurate time and dates would be nice, but the log file should be fine.

The car itself is just a testbed. It doesn't really need the remote control program, and doesn't need two adafruit motorshields. But as I said, it's a testbed to develop the code. The actual vehicle I'm planning on mounting all this in is a sub, but the way things are looking, that's at least a year away.

Once I've finished developing the program(s), I'll be putting it all in a boat first - slightly easier to waterproof, and easier to get into it and tweek things. Will also give me some experience installing waterproof shafts.

DesertPhoenix:
The actual vehicle I'm planning on mounting all this in is a sub,

I hope you are aware the 2.4GHz wireless won't penetrate water. It even has difficulty getting past a tree with wet leaves.

...R

Yeah, it was a bit of a shock when I found that out. It's the whole reason I'm developing the remote activated program in the first place.

I'm eventually going to have it capable of something like...

Motors ahead at full
Sink until pressure sensors read 2 metres deep, then lock ballast tank
Reduce motors to 1/2 speed
Slowly turn left till compass reads 180 degrees
run forward for 2 minutes
Rise until pressure reading reads 0 cm or wireless signals received
end program

Possibly with some sort of emergency override so it automatically surfaces after a set amount of time, or if pitch, yaw or roll exceed a certain amount

DesertPhoenix:
Yeah, it was a bit of a shock when I found that out. It's the whole reason I'm developing the remote activated program in the first place.

It may be worth doing some experiments with lower-frequency wireless such as 433MHz (or lower still if you can get legal devices).

...R

Yes, though I'm likely at least 6 months away from going back to the sub, so it may wait a bit. Still, it'd be very cool to be able to real-time control it under water.

Went away, built a bare-bones version of the ack-payload script (attached to edited first post, in case anyone wants it), took a break, went back to my main script and went through it again.

Found the error in a (supposedly) unrelated function.

Ack Payload had been changed from an array with 10 ints to an array with 2 ints. I had a function set up to wipe the array after it'd been sent, that looked something like this

  void returnArrayReset()
    {
      for (int i = 0; i <= 9; i++) 
      {
        returnArray[i] = 0;
      }
    }

For those of you who can't see it, that means it's wiping another 8 spaces somewhere else. I don't know where, but it was enough to stop it all working.

Marking this as resolved. Thanks for all your help Robin2.