0

I am new to Python and hope that someone can point me in the right direction about the logical issues I have with my code.

The program works fine running and behaves, as far as I can understand, as I expect. But now when the killswitch button is being pressed (and I can see in the output that the button is pressed and that the killswitch variable is set to True) the threads continue their work without stopping. When the button is pressed I want the kill_processes-method to run. Why doesnt the threads die gracefully and the program end as I wish? What have I missed here?

# Source: rplcd.readthedocs.io/en/stable/getting_started.html
#         https://www.circuitbasics.com/raspberry-pi-lcd-set-up-and-programming-in-python/

# LCD            R Pi                

# RS             GPIO 26 (pin 37)        
# E              GPIO 19 (35)
# DB4            GPIO 13 (33)
# DB5            GPIO 6  (31)
# DB6            GPIO 5  (29)
# DB7            GPIO 11 / SCLK (23)

# WPSE311/DHT11  GPIO 24 (18) 

# Relay Fog      GPIO 21 (40)
# Relay Oxy      GPIO 20 (38)
# Relay LED      GPIO 16 (36)

# Button Killsw. GPIO 12 (32)


# Imports--- [ToDo: How to import settings from a configuration file?]
import adafruit_dht, board, digitalio, threading 
import adafruit_character_lcd.character_lcd as characterlcd
import RPi.GPIO as GPIO
from time import sleep, perf_counter
from gpiozero import CPUTemperature
from datetime import datetime

# Compatible with all versions of RPI as of Jan. 2019
# v1 - v3B+
lcd_rs = digitalio.DigitalInOut(board.D26)
lcd_en = digitalio.DigitalInOut(board.D19)
lcd_d4 = digitalio.DigitalInOut(board.D13)
lcd_d5 = digitalio.DigitalInOut(board.D6)
lcd_d6 = digitalio.DigitalInOut(board.D5)
lcd_d7 = digitalio.DigitalInOut(board.D11)


# Define LCD column and row size for 16x2 LCD.
lcd_columns = 16
lcd_rows    = 2

# Initialise the lcd class---
lcd = characterlcd.Character_LCD_Mono(lcd_rs, lcd_en, lcd_d4, lcd_d5, lcd_d6,
                                      lcd_d7, lcd_columns, lcd_rows)


# Init sensors---
dhtDevice_nutrient_mist = adafruit_dht.DHT11(board.D24, use_pulseio=False)
#dhtDevice_xx = adafruit_dht.DHT22(board.Dxx, use_pulseio=False)
#dhtDevice = adafruit_dht.DHT11(board.D24, use_pulseio=False)


# Define relays
relay_fogger = 21 #digitalio.DigitalInOut(board.D21) #- Why does this not work?
relay_oxygen = 20 #digitalio.DigitalInOut(board.D20) #- Why does this not work?
relay_led    = 16 #digitalio.DigitalInOut(board.D16) #- Why does this not work?


# Init relays---
GPIO.setwarnings(False)
GPIO.setup(relay_fogger, GPIO.OUT)
GPIO.setup(relay_oxygen, GPIO.OUT)
GPIO.setup(relay_led, GPIO.OUT)


# Define liquid nutrient temperature probe
liquid_nutrients_probe = 16 #digitalio.DigitalInOut(board.D16) - Why does this not work?


# Define the killswitch push button
GPIO.setup(12, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)


# Global variables---
killswitch = False

# Fogger bucket vars
temp_nutrient_solution = 0
temp_nutrient_mist = 0
humidity_nutrient_mist = 0
fogger_on_seconds = 2700 #45 min
fogger_off_seconds = 900 #15 min
sleep_fogger = False

# Grow bucket vars
temp_roots = 0
humidity_roots = 0

# Oxygen bucket vars
sleep_oxygen = False

# Rapsberry Pi internal temperature
rpi_internal_temp = 0


# Methods---
def get_temp_nutrient_solution(killswitch): # Temp  för näringsväska. KLAR för TEST
    """Measure the temperature of the nutrient solution where the ultrasonic fogger is."""
    while not killswitch:
        global temp_nutrient_solution
        temp_nutrient_solution = 22
        #lcd.message = datetime.now().strftime('%b %d  %H:%M:%S\n')        
        #lcd.message = "Dummy temp liquid solution:/n {:.1f}C".format(temp_nutrient_solution)
        # For development process
        print(
            "T: {:.1f} C / {:.1f} F".format(
                temp_nutrient_solution, c2f(temp_nutrient_mist)
            )
        )
        sleep(1)


def get_temp_humidity_nutrient_mist(killswitch): # Temp och fuktighet för ånga. KLAR för TEST
    """Measure the tmeperature and humidity of the nutrient mist where the ultrasonic fogger is."""
    while not killswitch:
        try:
            # Update global temp value and humidity once per second
            global temp_nutrient_mist
            global humidity_nutrient_mist            
            temp_nutrient_mist = dhtDevice_nutrient_mist.temperature
            humidity_nutrient_mist = dhtDevice_nutrient_mist.humidity
            
            # For development process
            print(
                "T: {:.1f} C / {:.1f} F Humidity: {}% ".format(
                    temp_nutrient_mist, c2f(temp_nutrient_mist), humidity_nutrient_mist
                )
            )
 
        except RuntimeError as error:
            # Errors happen fairly often, DHT's are hard to read, just keep going
            print(error.args[0])
            sleep(1) # sleep(1) for DHT11 and sleep(2) for DHT22
            pass
        except Exception as error:
            dhtDevice.exit()
            kill_processes() # Förbättra denna här så att den ska visa vilken DHT-enhet som har fått error
            raise error
     
        sleep(1)


def relay_fogger_control(killswitch, sleep_fogger): # Fogger av eller på
    """Fogger on for 45 min and off for 15. Perpetual mode unless kill_processes() is activated"""
    while not killswitch or sleep_fogger:
        GPIO.output(relay_fogger, GPIO.HIGH)
        sleep(1)
        #sleep(fogger_on_seconds)
        GPIO.output(relay_fogger, GPIO.LOW)
        sleep(1)
        #sleep(fogger_off_seconds)


def relay_heatLED_control(killswitch): # Värmelampa LED av eller på
    """Heat LED controller. When is it too hot for the crops? Sleep interval? Perpetual mode unless kill_processes() is activated"""
    while not killswitch:
        GPIO.output(relay_led, GPIO.HIGH)
        sleep(3)
        #sleep(fogger_on_seconds)
        GPIO.output(relay_led, GPIO.LOW)
        sleep(3)
        #sleep(fogger_off_seconds)

def relay_oxygen_control(killswitch, sleep_oxygen): # Syremaskin av eller på
    """Oxygen maker. Perpetual mode unless kill_processes() is activated"""
    while not killswitch or sleep_oxygen:
        GPIO.output(relay_oxygen, GPIO.HIGH)
        sleep(5)
        #sleep(fogger_on_seconds)
        GPIO.output(relay_oxygen, GPIO.LOW)        
        #sleep(fogger_off_seconds)
        sleep(5)


def kill_processes(): # Döda alla processer
    """ToDo: A button must be pressed which gracefully kills all processes preparing for shutdown."""
    # Power off machines
    GPIO.output(relay_fogger, GPIO.LOW)
    GPIO.output(relay_led, GPIO.LOW)
    GPIO.output(relay_oxygen, GPIO.LOW)
    
    # Joined the threads / stop the threads after killswitch is true
    t1.join()
    t2.join()
    t3.join()
    t4.join()
    t5.join()
    #t6.join()
    
    reset_clear_lcd()
    # Stop message and GPIO clearing
    lcd.message = 'Full stop.\r\nSafe to remove.'
    GPIO.cleanup()
    

def reset_clear_lcd(): # Reset och rensa LCD
    """Move cursor to (0,0) and clear the screen"""
    lcd.home()
    lcd.clear()
    

def get_rpi_temp(): # Reset och rensa LCD
    """Move cursor to (0,0) and clear the screen"""
    global rpi_internal_temp
    cpu = CPUTemperature()
    rpi_internal_temp = cpu.temperature


def c2f(temperature_c):
    """Convert Celsius to Fahrenheit"""
    return temperature_c * (9 / 5) + 32


def lcd_display_data_controller(killswitch): # LCD display data controller
    """Display various measurments and data on the small LCD. Switch every four seconds."""
    while not killswitch:
        reset_clear_lcd()
        
        # Raspberry Pi internal temperature  
        lcd.message = (
            "R Pi (int. temp): \n{:.1f}C/{:.1f}F ".format(
                rpi_internal_temp, c2f(rpi_internal_temp)
            )
        )
        sleep(5)
        reset_clear_lcd()
        
        # Nutrient liquid temperature   
        lcd.message = (
            "F1: {:.1f}C/{:.1f}F ".format(
                temp_nutrient_solution, c2f(temp_nutrient_solution)
            )
        )
        sleep(5)
        reset_clear_lcd()
        
        # Nutrient mist temperature and humidity
        lcd.message = (
            "F2: {:.1f}C/{:.1f}F \nHumidity: {}% ".format(
                temp_nutrient_mist, c2f(temp_nutrient_mist), humidity_nutrient_mist
            )
        )
        sleep(5)
        reset_clear_lcd()

        # Root temperature and humidity
        lcd.message = (
            "R1: {:.1f}C/{:.1f}F \nHumidity: {}% ".format(
                temp_roots, c2f(temp_roots), humidity_roots
            )
        )
        sleep(5)
        reset_clear_lcd()


def button_callback(channel):
    global killswitch
    print("Button was pushed!")
    killswitch = True

# Init the button
GPIO.add_event_detect(12, GPIO.RISING, callback=button_callback)

# Create the threads
#tx = threading.Thread(target=xx, args=(killswitch,sleep_fogger,))
t1 = threading.Thread(target=get_temp_nutrient_solution, args=(killswitch,))
t2 = threading.Thread(target=get_temp_humidity_nutrient_mist, args=(killswitch,))
t3 = threading.Thread(target=relay_fogger_control, args=(killswitch,sleep_fogger,))
t4 = threading.Thread(target=lcd_display_data_controller, args=(killswitch,))
t5 = threading.Thread(target=get_rpi_temp)
#t6 = threading.Thread(target=killswitch_button)
 
 
# Start the threads
t1.start()
t2.start()
t3.start()
t4.start()
t5.start()
#t6.start()

# Code main process---
while not killswitch:
    sleep(1)

# Graceful exit
kill_processes()
Emperor 2052
  • 517
  • 1
  • 5
  • 14

1 Answers1

1

It might be possible to not pass the kill switch as a parameter, and instead treat it as a global from inside of the methods. I'm thinking that since it is a boolean (basic type) it is being passed by value (copied, the the value is just coming in a False and it is detached from the global definition) instead of passed by reference.

Maybe all you need to do, is remove the parameters that pass the function in (and remove them from the thread start calls) and just mark the variable in each method as global, like: global killSwitch

It looks like you do this in your button_callback method, but not in the other methods.

You can see examples here: Python creating a shared variable between threads

I hope this helps.

DMarczak
  • 729
  • 1
  • 9
  • 19