0

I have the below script to read a serial port from an Arduino on a Raspberry Pi. The intent is to have the Pi monitor the Arduino rfid output and when a particular card number is identified, to activate two different relays on a relay board. What's happening is the script essentially runs twice when the particular card number is identified. I can't figure out why it's doing that.

#!/usr/bin/python # -*- coding: utf-8 -*-

import serial
import time
import RPi.GPIO as GPIO

ser = serial.Serial('/dev/ttyACM0', 9600)
GPIO.setmode(GPIO.BCM)
# init list with pin numbers
pin_assignments = {'Disarm Alarm': 18, 'Unlock Door': 23}
GPIO.setup(18, GPIO.OUT)
GPIO.setup(23, GPIO.OUT)
GPIO.output(18, GPIO.HIGH)
GPIO.output(23, GPIO.HIGH)
while True:
    try:
        data = ser.readline() .decode("utf-8*)

        if "12 34 56 78" in data:
            time.sleep(2)
            GPIO.output(18, GPIO.LOW) # Disarm alarm
            print('Alarm Disarmed')
            time.sleep(1)
            GPIO.output(23, GPIO.LOW) # Unlock door
            print('Door Unlocked')
            time.sleep(3)
            GPIO.output(18, GPIO.HIGH)
            GPIO.output(23, GPIO.HIGH)
            print('Card Admitted')
        time.sleep(1)

        if data == 'no card select':continue

    except ser.SerialTimeoutException:
        print('Data could not be read')
        time.sleep(1)

...On a valid card read, I'm getting:

Alarm Disarmed Door Unlocked Card Admitted Alarm Disarmed Door Unlocked Card Admitted

Why do you think it's running through twice?

  • Because it's in a `while True` and you never break out of it, I'd guess -- not sure why **only** twice, I guess the `ser.readline()` is blocking the third time it gets called but I can't tell for sure (I don't have the HW at hand to reproduce the problem, alas). – Alex Martelli Jan 27 '15 at 01:52
  • Any suggestion on alternatives to while True? I'm new to Python and am not sure what would be the best alternative to run this script. I do need it to constantly monitor the Arduino serial output. – Seth Comire Jan 27 '15 at 02:04
  • A `break` when you don't want to keep repeating is the simplest idea, but that would stop the monitoring. If Arduino "stutters" and sends a line twice you may need to work around that, e.g with a `dict` of `data` values already seen to when they were last seen, so you can ignore repetitions that come too fast. Need some code in an answer to guide with that? If so let me know, thanks! – Alex Martelli Jan 27 '15 at 02:07
  • I definitely could use some code to guide me. I tried using `break` and that obviously didn't help me, like you said. – Seth Comire Jan 27 '15 at 02:14

1 Answers1

0

The problem seems to be that ser.readline can "stutter" and returns the same string twice (not sure why -- buffering? retries?). So what about ignoring "too-fast" (within say 300 seconds) duplicates? E.g:

import time
history = {}

while True:
    try:
        data = ser.readline().decode("utf-8")
        when = history.get(data, None)
        if when is not None and (time.time()-when) < 300:
            continue
        history[data] = time.time()

and the rest of the code unchanged. Essentially this ignores identical repetitions of a data line within 5 minutes (tweak the threshold to suit).

Any downside? Yep, history keeps growing, uselessly taking memory. It needs to be periodically re-built/pruned to keep only recent entries!

So for example, expanding the above...:

import time
history = {}
last_update = time.time()

while True:
    if time.time() - last_update > 600:  # 10 minutes, let's rebuild
        deadline = time.time() - 301
        history = dict((d,t)
                       for d, t in history.items()
                       if t > deadline)
        last_update = time.time()

    try:
        data = ser.readline().decode("utf-8")
        when = history.get(data, None)
        if when is not None and (time.time()-when) < 300:
            continue
        history[data] = time.time()

Again, the 600 (10 minutes) periodicity for rebuilds, and 301 (one more than 300:) for "what entries are worth keeping", can be tweaked to taste (balancing memory load vs CPU effort and responsivity). But this is one reasonable approach. There are more refined alternatives (e.g a log [list] of entries to selectively use for rebuilding or pruning) -- but, "entities must not be multiplied beyond necessity", so let's stick with simplicity until and unless more complexity proves necessary!-)

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • Thanks Alex, this worked great. I lowered the time to 15 seconds because any card reads after the first read wouldn't respond. Once I lowered it, it worked flawlessly. – Seth Comire Jan 27 '15 at 15:29