2

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.

For the rotary encoder, I'm using code I found here (Rotary Encoder library for RPI), and for the sonar, I’m attempting to use this code I found on the RPI forums.

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?

2 Answers 2

2

Have a look at how I implement a Sonar Ranger and a Rotary Encoder as Python classes using GPIO callbacks with my pigpio library.

A GPIO callback is a method of being informed of GPIO level changes asynchronously of the main thread. The callback can set a flag or if a class a method can be added to get the current state.

You should be able to do something similar in your code.

0
0

I hooked up a rotary encoder using the device tree and /lib/modules/uname -r/kernel/drivers/input/misc/rotary_encoder.ko

It generates interrupt events so you write an event handler that sets the volume when the knob moves. No poll loop needed. You set the out format to what you want. You probably would want be able to set the volume to 11. :-)

All you need to know is in the comments links. Questions or progress welcome.

Compile the dts below into a dtb and copy it to /boot/overlays. Overrides are parameter you can set in /boot.config.txt See the first link.

/*
 * rotary-overlay.dts
 * Links:
 * https://www.raspberrypi.org/documentation/configuration/device-tree.md
 * https://www.kernel.org/doc/Documentation/input/rotary-encoder.txt
 * https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/drivers/input/misc/rotary_encoder.c
 */

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709";

    fragment@0 {
        target-path = "/soc/gpio";
        __overlay__ {
            knob_pins: knob_pins {
                brcm,pins = <13 27>;
                brcm,function = <0>;
                brcm,pull = <2>;
            };
        };
    };

    fragment@1 {
        target-path = "/soc";
        __overlay__ {
            knob: knob {
                compatible = "rotary-encoder";
                #address-cells = <1>;
                #size-cells = <0>;
                pinctrl-names = "default";
                pinctrl-0 = <&knob_pins>;
                gpios = <&gpio 13 1>, <&gpio 27 1>; 
                linux,axis = <0>; /* REL_X */
                rotary-encoder,relative-axis;
            };
        };
    };
    __overrides__ {
        relative_axis =  <&knob>,"rotary-encoder,relative-axis";
        linux_axis =  <&knob>,"linux,axis";
        rollover =  <&knob>,"rotary-encoder,rollover";
        half-period =  <&knob>,"rotary-encoder,half-period";
        steps =  <&knob>,"rotary-encoder,steps";
    };
};

I just let SDL handle the events for a graphic MP3 player I wrote.

1
  • i know this is an old question/answer, but any info on how i would set up an event handler? preferably in python or something to directly turn the events into key events (volume up and down)
    – James Kent
    Mar 28, 2018 at 14:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.