Serial.available() only true if serial monitor window is open

I wrote a small Python program that sends strings over a USB connection. It is very simple:

#!/usr/bin/env python3

import argparse
import serial

parser = argparse.ArgumentParser(description='Send strings over USB')
parser.add_argument('--string', required=True, help='The string to send')
parser.add_argument('--port', required=False, help='USB port to use, defaults to /dev/ttyUSB0')
args = parser.parse_args()

cmd = vars(args)["string"]
port = vars(args)["port"]

if port is None or port == "":
	port = "/dev/ttyUSB0"

ser = serial.serial_for_url(port, do_not_open=True)
ser.baudrate = 9600
ser.open()

if cmd is not None:
	ser.write(str.encode(cmd))
	ser.flush()

On my arduino, I have connected an RF24L01 tranceiver. I wrote a simple sketch that reads from serial input and then transmits whatever is read.

#include <RF24_config.h>
#include <RF24.h>
#include <nRF24L01.h>
#include <printf.h>

#include <SPI.h>
#include <nRF24L01.h>
#include <RF24.h>
      
RF24 radio(9, 10); // CE, CSN

const byte address[6] = "00001";

void setup() {
  radio.begin();
  radio.openWritingPipe(address);
  radio.setPALevel(RF24_PA_MIN);
  radio.stopListening();
  pinMode(4, OUTPUT);
  Serial.begin(9600);
  Serial.println("arduino ready ro recieve");
}

void loop() {
  if (Serial.available()) {
    digitalWrite(4, HIGH);
    String command = Serial.readString();
    char CMD[7];
    command.toCharArray(CMD, sizeof(CMD));
    radio.write(&CMD, sizeof(CMD));
  }
}

This code works perfectly fine so long as I have the serial monitor window open. If the serial monitor window is not open, then the Serial.available() is never true. I have tested this by adding a led, and tried it on both an Uno and a Nano.

I am certain I must be missing something obvious. I would very much appreciate any help understanding what I am doing wrong, and why opening the serial monitor window would determine if my code works or not.

I think the radio code is adding unnecessary complexity. It would be better if you reduced your sketch to the minimum possible to receive messages from Serial and blink and LED.

I recommend adding some LED blinking code to setup() that is distinctive (e.g. fast blinks if your other LED code does slow blinks). The Arduino boards have auto-reset circuits that reset the microcontroller when a serial connection is made with your computer. So it may be that the Python code is resetting your Arduino. With some distinctive blinks in setup() this will be obvious.

Thank you so much. I followed your debugging advice and checked Google, and, in fact, receiving the signal from the Python code was resetting the arduinos. The Python code was then sending the string over USB faster than the arduino could re-set itself up. I fixed it by simply adding a 1 second sleep to the Python code:

#!/usr/bin/env python3

import argparse
import serial
from time import sleep 

parser = argparse.ArgumentParser(description='Send strings over USB')
parser.add_argument('--string', required=True, help='The string to send')
parser.add_argument('--port', required=False, help='USB port to use, defaults to /dev/ttyUSB0')
args = parser.parse_args()

cmd = vars(args)["string"]
port = vars(args)["port"]

if port is None or port == "":
 port = "/dev/ttyUSB0"

ser = serial.serial_for_url(port, do_not_open=True)
ser.baudrate = 9600
ser.open()
sleep(1)

if cmd is not None:
 ser.write(str.encode(cmd))
 ser.flush()

Now it works reliably. Thanks again.

I'm glad to hear you found the solution. Enjoy!
Per