Troubleshooting IR Sensor RPi Project: Seeking Guidance

Hi everyone,

I’m working on my first Raspberry Pi project and could use some guidance. I’m building a theremin-like digital instrument using two SHARP GP2Y0A51SK0F IR sensors. Each sensor is supposed to trigger one of five audio clips based on distance.

Here’s what I’ve done so far:

  • I’ve set distance thresholds for each audio clip (e.g., 14-15cm triggers Clip 1, 11-13cm triggers Clip 2, etc.).
  • I’ve written code to handle sensor input and play the clips accordingly.

However, I’m encountering a few issues:

  • Erratic Playback: The audio clips play erratically and seem to be cut off at the low and high ends of the distance range.
  • Persistent Playback: When no object is in front of the sensors, one clip plays continuously until something comes into range.

I’m wondering if:

  • There might be improvements or adjustments I can make in the code to address these issues.
  • The IR sensors might not be ideal for this application, and if alternatives like ultrasonic or Time-of-Flight sensors could be better (ideally want to solve with coding before buying new hardware)

Here’s my code for reference:

import RPi.GPIO as GPIO
import time
import spidev
from subprocess import call
import threading
import statistics

# Setup SPI
spi = spidev.SpiDev()
spi.open(0, 0)
spi.max_speed_hz = 1350000

# MCP3008 channel configuration
sensor1_channel = 0
sensor2_channel = 1

# Distance to audio mapping with updated intervals
distance_ranges = [(14, 15), (11, 13), (8, 10), (5, 7), (2, 4)]
audio_files_sensor1 = [f"/home/jss/audio_clips/s1_clip{i+1}.m4a" for i in range(len(distance_ranges))]
audio_files_sensor2 = [f"/home/jss/audio_clips/s2_clip{i+1}.m4a" for i in range(len(distance_ranges))]

# Smoothing parameters
smoothing_window_size = 5
sensor1_data = []
sensor2_data = []

def read_adc(channel):
    adc = spi.xfer2([1, (8 + channel) << 4, 0])
    data = ((adc[1] & 3) << 8) + adc[2]
    return data

def get_distance(sensor_channel, sensor_data):
    adc_value = read_adc(sensor_channel)
    distance = (adc_value * 3.3 / 1024) * 30

    # Smoothing the distance using a simple moving average
    sensor_data.append(distance)
    if len(sensor_data) > smoothing_window_size:
        sensor_data.pop(0)

    smoothed_distance = statistics.mean(sensor_data)
    print(f"Smoothed distance from sensor {sensor_channel}: {smoothed_distance}")  # Debugging
    return smoothed_distance

def play_audio(file):
    try:
        print(f"Playing audio file: {file}")  # Debugging
        call(["mpv", file])
    except Exception as e:
        print(f"Error playing audio file: {file}. Error: {e}")

def handle_sensor(sensor_channel, audio_files, sensor_data):
    last_played_index = -1
    while True:
        distance = get_distance(sensor_channel, sensor_data)
        for i, (low, high) in enumerate(distance_ranges):
            if low <= distance <= high:
                if i != last_played_index:
                    play_audio(audio_files[i])
                    last_played_index = i
                break
        time.sleep(0.1)

def main():
    print("Script started...")  # Debugging

    # Create and start threads for each sensor
    sensor1_thread = threading.Thread(target=handle_sensor, args=(sensor1_channel, audio_files_sensor1, sensor1_data))
    sensor2_thread = threading.Thread(target=handle_sensor, args=(sensor2_channel, audio_files_sensor2, sensor2_data))

    sensor1_thread.start()
    sensor2_thread.start()

    # Wait for both threads to finish (they won't in this case)
    sensor1_thread.join()
    sensor2_thread.join()

if __name__ == "__main__":
    main()
2 Likes

Implementing a simple moving average for distance smoothing will be good I think. However, ensure that the window size (smoothing_window_size) is appropriate. Too small a window might not smooth out noise effectively, while too large a window could introduce significant delays.

3 Likes

Hi Sawyer,

Welcome to the forum! What an exciting project you have here, instrument projects open you up to many different code and hardware challenges. If you could some images of your project so far that would be lovely, mostly so we can get an idea of what components you are using and where these are wired.

@lia262073 also has a great suggestion there. We’d love to here how trying that goes!

1 Like

Nice @Sawyer276236

I wonder whether I would push my audio to it’s own thread and handle the sensors in the main loop. i.e.

# I appreciate this code is not clean
# Just sketching some ideas

def getAudioFileForRange(x,y):
   return the_audio_to_play_when_at_this_distance

def main():
   for low, high in distance_ranges:
      if sensor1.get_distance() in range(low, high);
         #you might kill previous thread here?
         threading.Thread(target=play_audio, args=my_audio_file.mp4)
      if sensor2.get_distance() in range(low, high);
         my_audio = getAudioFileForRange(low,high)
         threading.Thread(target=play_audio, args= my_audio)

I don’t think the idea of calling in a sub-process is bad, just appreciate that you’re really throwing the audio to the kernel to handle. call() will spin up a new thread every time (kinda). That’s a thread you don’t control and I can see how that would lead to some sporadic audio.

I’m essentially suggesting you take control over the audio. That’s comes with added complexity. To manage that complexity I’m wondering if you’d consider keeping the sensor readouts in the main loop, leaving other threads to manage your audio.

Don’t forget that the threading module is still under the banner of pythons global interpreter lock, and it’s not like these two threads were ever running simultaneously.

That doesn’t mean you were not saving time, or that the threading was useless.
If you want the sensors to handle be on separate threads too you could give them their own thread and use a global memory buffer to synchronize. :slight_smile:

Just some thoughts. Very cool project.
Pix :heavy_heart_exclamation:

2 Likes

Sorry for my delay in reply, but thank you so much for your great suggestions! Is there another way to add a moving average for distance smoothing other than the get_distance function with sensor_data and calculating the average of the most recent readings (up to the window size)? That’s what I currently have now and not sure if its what you are talking about or not.

As for the smoothing window size, I went up from 5 to 10 and it significantly reduced the amount of sounds triggered when there is nothing in front of the sensors, which is fantastic, exactly what I was hoping for. I am still not getting accurate readings from the sensors though in relation to the audio clips and I’m not sure why. For each sensor there should be 5 audio clips triggered at increments of 2cm, (Sensor 1 = clips 1-5, Sensor 2 = clips 6-10) and its still kind of playing somewhat random clips at the incorrect distances. Perhaps I messed something up in the code in how that is structured and I just can’t see it clearly and have to keep experimenting.

Again, thank you for your suggestions here, I’m a bit closer!

1 Like

Thanks so much, and for sure, here is my setup!

1 Like