So, I'm making what's turning out to be a pretty difficult project for me to code. In short, I'm making a speaker that will play audio when you get near it (thanks to a sonar), with a knob interface (rotary encoder) that allows you to change volume and/or track. And I’m writing it all in Python.
But, here's the rub: because it's a rotary encoder, I have to be constantly checking for rotation changes, and with the sonar, I have to constantly be checking to see if anybody's near. So, both these constant loops need to be running at essentially the same time and in the background.
I've done a fair amount of research into multithreading, multiprocessing, and asynchronous code and I can't seem to find a solution that works correctly.
The problem is, when I run the sonar code from above in a process or thread, it seems to misread and give a lot of crap data, so it’s not trustworthy. When it’s alone, it’s fine.
Right now my most recent attempt has me starting a thread timer for the sonar that restarts every 3 seconds.
def sonar_handler(): print "sonar handler starting" distance = measure_average() print " Distance : %.1f cm" % distance threading.Timer(3, sonar_handler).start()
BUT, I have to have the knob working on a constant loop on its own thread, and when I add this into the loop, I get constant bad readings from my sonar:
def knob_handler(): while True: global last_state delta = encoder.get_delta() if delta!=0: print "rotate %d" % delta if delta>0: volume_up(delta) if delta<0: volume_down(delta) sw_state = switch.get_state() if sw_state != last_state: print "switch %d" % sw_state last_state = sw_state try: knob_thread = threading.Thread(target=knob_handler) knob_thread.start()
In case it's helpful, here's the code that the sonar_handler() is referring to:
def measure(): # This function measures a distance # Pulse the trigger/echo line to initiate a measurement GPIO.output(GPIO_TRIGECHO, True) time.sleep(0.00001) GPIO.output(GPIO_TRIGECHO, False) #ensure start time is set in case of very quick return start = time.time() # set line to input to check for start of echo response GPIO.setup(GPIO_TRIGECHO, GPIO.IN) while GPIO.input(GPIO_TRIGECHO)==0: start = time.time() # Wait for end of echo response while GPIO.input(GPIO_TRIGECHO)==1: stop = time.time() GPIO.setup(GPIO_TRIGECHO, GPIO.OUT) GPIO.output(GPIO_TRIGECHO, False) elapsed = stop-start distance = (elapsed * 34300)/2.0 time.sleep(0.1) return distance def measure_average(): # This function takes n measurements ignoring any rogue values # returns the average. MAX_TRIES = 5 # Attempts to get consistent results MEASURE_COUNT = 3 # Nof raw measures in each attempt CHECK = 2.0 # tolerance in cm between measurements midpoint = MEASURE_COUNT / 2 for tries in range(MAX_TRIES): distances =  for i in range(MEASURE_COUNT): distances.append(measure()) distances.sort() measureOK = True for i in range(MEASURE_COUNT - 1): if abs(distances[i] - distances[midpoint]) > CHECK: measureOK = False break if measureOK: break print "Inconsistent results. ", distances, " Retrying.." if measureOK: distance = sum(distances) / len(distances) else: print "Inconsistent after retries. Best guess value" distance = distances[len(distances)/2] return distance
At the end of the day, is there an easier, more straightforward way to do this?