0

This is my python module that is being ran as two threads in main. One thread runs monitor_wind() which adds to wind_count every spin and another thread runs calculate_speed() to get wind in mph from the count, and add it to a list that is later averaged for average wind in mph.

My problem is that calculate_speed() doesn't seem to be accessing the global variable wind_count, it is always 0 even when monitor_wind() is adding to. But the changes it makes to wind_list can be seen in the globals() list.

How can I get it to access the global variable?

from gpiozero import Button
import math
from time import sleep

wind_sensor = Button(6)
wind_count = 0
wind_list = []

def calculate_speed():
    while True:
        global wind_list, wind_count
        print(wind_count)
        radius_cm = 9.0
        cm_in_mile = 160934.4
        circumference_cm = (2 * math.pi) * radius_cm
        rotations = wind_count / 2.0
        dist_miles = (circumference_cm * rotations) / cm_in_mile
        speed = dist_miles / 5 # Divide distance by time, 5 seconds
        print(speed)
        wind_list.append(speed)
        wind_count = 0 # Reset wind count
        sleep(5)

def spin():
    global wind_count
    wind_count += 1

def monitor_wind():
    wind_sensor.when_pressed = spin
6
  • Do you ever call monitor_wind? The simple explanation is that spin isn't being called, which means that the callback is never being registered, or the callback isn't being called. Please show a minimal reproducible example. Commented Apr 9, 2021 at 17:39
  • Yes spin is being called. It started as a thread in main.py. I have tested it by adding print statements to monitor_wind and watching wind_count increment. Commented Apr 9, 2021 at 17:55
  • from wspeed import monitor_wind, calculate_speed, wind_avg, wind_list th_wmonitor = th.Thread(target=monitor_wind, daemon=True) th_wspeed = th.Thread(target=calculate_speed, daemon=True) th_wspeed.start(); th_wmonitor.start() Commented Apr 9, 2021 at 18:10
  • 1
    monitor_wind looks like it doesn't actually run the spin function, it just binds spin to your wind_sensor button. Is there a reason you don't just bind it once at the start? It looks like if you do that, spin is still run every time the button is pressed. Commented Apr 9, 2021 at 20:25
  • This portion of my program is/was actually working correctly. I thought the variable wasn't being accessed because the average was coming out to 0.0 when there were readings. This was because I was including the 0.0 wind readings in the average, which after rounding made the result 0 wind average. I've since added an if statement to execute calculate_speed if wind_count is not 0 Commented Apr 12, 2021 at 15:23

1 Answer 1

1

To me, this does not appear to be an issue with your use of globals, though I would discourage using globals, and instead make a class WindMonitor. You should also use a lock if you are accessing a variable from multiple threads.

Try this out (I had to call spin on random time intervals because I don't have gpios on my device).

wspeed.py

# from gpiozero import Button
import math
import random
import threading
from time import sleep

DEFAULT_WIND_SENSOR = None; # Button(6)

class WindMonitor:
    def __init__(self, button=DEFAULT_WIND_SENSOR):
        self.wind_count = 0
        self.wind_list = []
        self.button = button
        self.wind_count_lock = threading.Lock()
    
    def calculate_speed(self, stop_event):
        while True:
            with self.wind_count_lock:
                print(self.wind_count)
                radius_cm = 9.0
                cm_in_mile = 160934.4
                circumference_cm = (2 * math.pi) * radius_cm
                rotations = self.wind_count / 2.0
                dist_miles = (circumference_cm * rotations) / cm_in_mile
                speed = dist_miles / 5 # Divide distance by time, 5 seconds
                print(speed)
                self.wind_list.append(speed)
                self.wind_count = 0 # Reset wind count
            if stop_event.wait(5):
                return
    
    def spin(self):
        with self.wind_count_lock:
            self.wind_count += 1

    def monitor_wind(self):
        # self.button.when_pressed = self.spin
        while True:
            sleep(float(random.randint(1, 10)) / 10)
            self.spin()

__main__.py

import threading as th
import time
from wspeed import WindMonitor

if __name__ == '__main__':
    monitor = WindMonitor()
    stop_event = th.Event()
    th_wmonitor = th.Thread(target=monitor.monitor_wind, daemon=True)
    th_wspeed = th.Thread(target=monitor.calculate_speed, args=[stop_event], daemon=True)
    try:
        th_wspeed.start()
        th_wmonitor.start()
        while True: time.sleep(100)
    except (KeyboardInterrupt, SystemExit):
        stop_event.set()

Edit: Explaining stop event

All of your threads loop forever (main thread, th_wmonitor, and th_wspeed). You want the program to exit when the main terminates.

When python exits, it waits for all non-daemonic threads to terminate, so you did the right thing by making them daemonic. The problem is that python will only exit when there is a single remaining daemonic thread. Because both daemonic threads loop infinitely, you need a way to tell one (or both) of them to when the program exits.

The simplest way of achieving this is to use a threading.Event object. An Event is a simple way of communicating between threads. It's basically a flag that you set in one thread and check in another. By having an exception in main, you can set the stop_event instance of threading.Event. This lets any thread with access to stop_event that the main thread has exited, and other threads may terminate when they are ready to terminate.

The th_wspeed needs to terminate at a specific time because you don't want to terminate in the middle of a print statement. For that reason, I made th_wspeed the one to check stop_event. When th_wspeed sees stop_event has been set, it returns, thus terminating one of the two daemonic threads. At that point, because python sees that only one daemonic thread remains, it will force that thread to terminate, and the program exits.

Without the stop event, the program would not terminate when you, for example, press CTRL+C.

Sign up to request clarification or add additional context in comments.

4 Comments

Thank you! I know I need to be using classes, especially now that I have several other modules with global variables and threads for other sensors. I just wasn't sure about the init but it looks like it's a constructor. I'm reading the official class documentation to get more familiar. Can you explain what the stop event is, and how it's working in your example?
Too long to put in a comment. Updated the answer with a response.
Forgot to mention that the reason you should th_wmoniter as a daemonic thread is that you do not want it to terminate immediately. You want it to terminate after th_wspeed so your last speed printed is accurate you could a spin if th_wmonitor terminates before th_wspeed.
Updated again. Previously, th_wspeed would wait as much as 5 seconds before terminating once stop_event was set. It will now terminate immediately after stop_event is set.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.