Python won't read serial data the same way that the Arduino Serial Monitor does

First post, so forgive me if this is in the wrong place, too detailed, not detailed enough, etc....

I'm trying to build an Arduino based alarm system - A few (very bright) LEDs point at an LDR, and when then LEDs are turned off / moved, the arduino sounds the alarm. The idea, of course, being that when the LDR finds the light to be much less, it does .

consists of writing to serial "1", which a python program on my computer should intemperate, and do it's part, until a "0" is written to Serial, which happens when a button is pressed. This essentially 'deactivates' the alarm.

When watching the serial communication in the arduino IDE (tools > Serial Monitor), all is well - "1" appears when the light is removed, and "0" appears once the light is back in place and the button has been pressed.

However - when looking at the same serial communications from my Python program, only "1" is printed, and it continues to print repeatedly until the light is put back infront of the LDR. Upon pushing the button to print a "0", nothing happens.

Anyway, Here's my code:

Arduino:

const int buttonPin = 2;
const int Buzzer1 = 3;
const int LDR = A0;

int LDRValue = 0;
int buttonState = 0;

int alarmActivated = 0; //0 = no, 1 = yes

void setup() {
  // initialize the piezo as output:
  pinMode(Buzzer1, OUTPUT);  
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT); 
  Serial.begin(9600);    
}

void loop(){
  buttonState = digitalRead(buttonPin);
  LDRValue = analogRead(LDR);
  //Serial.println(LDRValue);
  if ((LDRValue < 1000) && (alarmActivated == 0)) {
    Serial.print("1");
    Serial.print("\n"); 
    tone(Buzzer1, 500, 500);
    //door is open
    alarmActivated = 1;
  }

  if ((alarmActivated == 1) && (buttonState == HIGH)) {
    Serial.print("0");
    Serial.print("\n"); 
    alarmActivated = 0;
    noTone(Buzzer1);
  }
}

Python:

import serial

def readSerial(port, baudrate):
    arduinoData = serial.Serial(port, baudrate)
    arduinoSerial = (arduinoData.readline().strip())
    text = arduinoSerial.decode('utf-8')
    return text

def doYourThingPython():
    #not yet coded :(

    #if line = 1, play sound1
    #if line = 0, stop sound1

while True:
    for line in readSerial('/dev/ttyUSB0', 9600):
        print(line)
        doYourThingPython(line)

Does pin 13 on your Arduino blink when you run your Python code?

Yes. The LED does blink when the Python program is started, and every time a "1" is printed, which doesn't make sense - to my knowledge, this means that the arduino is actively sending "1"s to the computer, however if I close the Python program and open the serial monitor, the LED only blinks once when a single "1" is sent, and once when a single "0" is sent. There is no repeats.

How much do you want to learn?

(Versus how quickly do you want an answer.)

While waiting for an answer...

Normally, why does pin 13 blink?

Try running each line of Python interactively. Which line of Python code causes pin 13 to blink?

I don't urgently need an answer - I've got a few weeks. Happy to learn a bit.

As for what part of the python code blinks the LED, I've got no idea - will do some testing shortly and get back to you...

Well, I learned something.

Upon running the code line-by-line, I found that it stalls on

arduinoSerial = (arduinoData.readline().strip())

And that's where the TX LED flashes, and if I read the data from the new variable, I get my '1'.

If I run all the code again, Pin 13 flashes on the first line - and that's what I learned. The Arduino is being reset each time the Python function runs. That means that the alarmActivated integer is reset to 0.

So my new question: How do we avoid this? Do I have to write the variable to Serial, then read them back after the reset? Surely there is a better way...

This Python - Arduino demo may be of interest.

...R

Doofitator:
The Arduino is being reset each time the Python function runs.

Excellent analysis. Specifically, it is auto-resetting.

So my new question: How do we avoid this?

The answer to that question lies in what exactly causes the auto-reset. Is opening the serial port the trigger? Is reading from the serial port the trigger? Is something else the trigger? Once you know the answer you may be able to restructure your Python to not only avoid the auto-reset but to use it to your advantage.

See if you can determine exactly which line of Python triggers the auto-reset.

Okay - I have found what exactly each line does:

arduinoData = serial.Serial('/dev/ttyUSB0', 9600) #This line causes the auto-reset (Pin 13 flash)
arduinoSerial = (arduinoData.readline().strip()) #This line pauses until input is recieved (Pin RX flash)
text = arduinoSerial.decode('utf-8') #Just Python - does not interface with Arduino.

I believe that it is opening the Serial port that is the trigger for auto reset. This means that the way around it is to only open the serial port once, maybe at the start of the python code outside of the function (which works. I tested it), or is there also a way to close the serial port at the end of the function, to avoid the reset?

Doofitator:
I believe that it is opening the Serial port that is the trigger for auto reset. This means that the way around it is to only open the serial port once, maybe at the start of the python code outside of the function (which works. I tested it), or is there also a way to close the serial port at the end of the function, to avoid the reset?

Your analysis is correct.

And because your analysis is correct your question does not make sense - as you yourself said, it is not the closing that causes the reset.

The demo code I posted a link to keeps the serial port open. Also, when it opens the serial port it waits to get a message from the Arduino so the PC knows that the Arduino is ready.

...R

Doofitator:
I believe that it is opening the Serial port that is the trigger for auto reset.

Excellent!

This means that the way around it is to only open the serial port once, maybe at the start of the python code outside of the function (which works. I tested it)

Exactly.

(I would change your function to an object that encapsulates that detail thus avoiding both the auto-reset trouble and keeping the global namespace lean.)

...or is there also a way to close the serial port at the end of the function, to avoid the reset?

There are other ways to prevent auto-reset from firing...

  • Disable auto-reset

  • Cut the RESET-EN trace on the board

  • Or, add a capacitor to your circuit

  • Prevent DTR from being asserted. Under Windows this requires modifying the program. I believe Python has some options to keep DTR low. I believe Linux has some terminal configuration that can be used to keep DTR low for all applications.

In any case, for most applications, opening the serial port once is a good choice.

Awesome. Thanks to both of you! I'll leave the python to only open the serial port once, right at the start. That should solve my problem!!

If your Arduino board is using the 8u2/16u2 AVR chip for the USB to serial conversion, a 2 line patch to the source code in that chip will would also fix this type of issue.
It changes the auto reset to use RTS instead of DTR.

The IDE and avrdude trigger/toggle RTS after opening the port for a download, (and have for MANY years - pretty much since auto reset was added to Arduino) so there really is no need to use DTR for auto reset these days.

By using RTS, the autoreset only occurs when the application toggles RTS instead of blindly when the the serial port is opened.
This allows an application to open and use the serial port without resetting the Arduino.
This is the behavior you get if you use the original FTDI cable on something like an Arduino "pro" board since the original FTDI board didn't bring out DTR but rather RTS.

Also, if RTS were consistently used instead, the IDE serial monitor could even have an option/button to reset the arduino (by toggling RTS) without having to close and reopen the serial port.

IMO, the 16u2 code should be redone to have two USB endpoints.
one for virtual TTY and the other to emulate either USBtiny or USBasp.
That way no bootloader would be needed in the Arduino AVR chip.
IMO, it seems silly to have an AVR chip sitting there that only does serial over USB when it could also do ISP programming of the Arduino AVR processor as well.

---bill