22import psutil
33import subprocess
44import sys
5- from threading import Thread
5+ from threading import Thread , Lock
66import time
7- # from plant_classification import read_v
87import Jetson .GPIO as GPIO
9- # except (RuntimeError, ModuleNotFoundError):
10- # class MockGPIO:
11- # BCM = BOARD = IN = OUT = HIGH = LOW = None
12- # def setmode(self, mode): pass
13- # def setup(self, pin, mode): pass
14- # def output(self, pin, state): pass
15- # def input(self, pin): return False
16- # def cleanup(self): pass
17- # def wait_for_edge(self, pin, edge_type):
18- # print(f"Simulating waiting for {edge_type} edge on pin {pin}")
19- # return True
20- # GPIO = MockGPIO()
8+ import logging
219
22- def kill_python_scripts_by_name (target_names ): # ex ['lighting_rainy.py', 'lighting_summer.py']
23- """
24- Kill all running Python scripts whose command lines include one of the target names.
25- Does not kill the currently running script.
26- """
27- current_pid = os .getpid ()
28- for proc in psutil .process_iter (['pid' , 'name' , 'cmdline' ]):
29- try :
30- if proc .info ['pid' ] == current_pid :
31- continue
32- cmdline = proc .info ['cmdline' ]
33- if cmdline and 'python' in cmdline [0 ].lower ():
34- for name in target_names :
35- if any (name in part for part in cmdline [1 :]): # check script path/args
36- print (f"Killing PID { proc .info ['pid' ]} : { ' ' .join (cmdline )} " )
37- proc .kill ()
38- break
10+ # Configure logging
11+ logging .basicConfig (level = logging .INFO , format = '%(asctime)s - %(levelname)s - %(message)s' )
12+ logger = logging .getLogger (__name__ )
3913
40- except (psutil .NoSuchProcess , psutil .AccessDenied ):
41- continue
14+ # GPIO Pin Configuration
15+ ONOFF_PIN = 16 # Physical 21
16+ SEASONS = {
17+ 'rainy' : {'pin' : 19 , 'script' : 'rainy_sound.py' }, # Physical 35
18+ 'spring' : {'pin' : 8 , 'script' : 'spring_sound.py' }, # Physical 24
19+ 'winter' : {'pin' : 4 , 'script' : 'winter_sound.py' } # Physical 7
20+ }
4221
43- def run_script (script_name ): # ex 'lighting_rainy.py'
44- script_path = os .path .join (os .path .dirname (__file__ ), script_name )
45- print (f"Running script: { script_path } " )
46- subprocess .Popen ([sys .executable , script_path ])
47-
48- GPIO .setwarnings (False )
49- GPIO .cleanup ()
50- GPIO .setmode (GPIO .BCM )
51-
52- winter_pin = 4 #physical 7
53- spring_pin = 8 #physical 24
54- rainy_pin = 19 #physical 35
22+ running_processes = []
23+ process_lock = Lock ()
5524
56- GPIO .setup (rainy_pin , GPIO .IN ) # button pin set as input
57- GPIO .setup (spring_pin , GPIO .IN ) # button pin set as input
58- GPIO .setup (winter_pin , GPIO .IN ) # button pin set as input
25+ def kill_python_scripts_by_name (target_names ):
26+ """Kill running Python scripts matching target names."""
27+ with process_lock :
28+ for proc in running_processes [:]:
29+ try :
30+ if proc .poll () is None :
31+ cmdline = proc .cmdline ()
32+ for name in target_names :
33+ if any (name in part for part in cmdline [1 :]):
34+ logger .info (f"Terminating PID { proc .pid } : { ' ' .join (cmdline )} " )
35+ proc .terminate ()
36+ try :
37+ proc .wait (timeout = 2 )
38+ except subprocess .TimeoutExpired :
39+ logger .warning (f"Force killing PID { proc .pid } " )
40+ proc .kill ()
41+ running_processes .remove (proc )
42+ break
43+ except (psutil .NoSuchProcess , psutil .AccessDenied ):
44+ running_processes .remove (proc )
5945
60- ss_old = ''
61- ss_new = 'spring'
46+ def run_script (script_name ):
47+ """Run a Python script in a subprocess and track its process."""
48+ script_path = os .path .join (os .path .dirname (__file__ ), script_name )
49+ if not os .path .exists (script_path ):
50+ logger .error (f"Script { script_path } not found." )
51+ return None
52+ try :
53+ logger .info (f"Running script: { script_path } " )
54+ proc = subprocess .Popen ([sys .executable , script_path ])
55+ with process_lock :
56+ running_processes .append (proc )
57+ return proc
58+ except Exception as e :
59+ logger .error (f"Failed to run { script_path } : { e } " )
60+ return None
6261
6362class choose_season (Thread ):
63+ """Thread to handle season selection based on GPIO button presses."""
6464 def __init__ (self ):
6565 Thread .__init__ (self )
6666 self .running = True
67+ self .ss_old = ''
68+ self .ss_new = 'spring'
69+ for season , config in SEASONS .items ():
70+ GPIO .add_event_detect (
71+ config ['pin' ], GPIO .RISING , callback = self .handle_button , bouncetime = 200
72+ )
73+
74+ def handle_button (self , pin ):
75+ for season , config in SEASONS .items ():
76+ if pin == config ['pin' ]:
77+ self .ss_new = season
78+ logger .info (f"{ season .capitalize ()} season selected." )
79+ if self .ss_new != self .ss_old :
80+ self .ss_old = self .ss_new
81+ kill_python_scripts_by_name ([config ['script' ] for config in SEASONS .values ()])
82+ run_script (SEASONS [self .ss_new ]['script' ])
83+ self .ss_new = ''
84+ break
85+
6786 def stop (self ):
6887 self .running = False
69- def run (self ):
70- global ss_old , ss_new
71- while self .running :
72- while len (ss_new ) == 0 :
73- if GPIO .input (rainy_pin ) == GPIO .HIGH :
74- ss_new = 'rainy'
75- print ("Rainy season selected." )
76- break
77- elif GPIO .input (spring_pin ) == GPIO .HIGH :
78- ss_new = 'spring'
79- print ("spring season selected." )
80- break
81- elif GPIO .input (winter_pin ) == GPIO .HIGH :
82- ss_new = 'winter'
83- print ("Winter season selected." )
84- break
85- time .sleep (0.1 )
86-
87- if ss_new == ss_old :
88- pass
89- else :
90- ss_old = ss_new [:]
91- kill_python_scripts_by_name (['spring_sound.py' , 'rainy_sound.py' ,'winter_sound.py' ])
92- if ss_new == 'rainy' :
93- run_script ('rainy_sound.py' )
94- elif ss_new == 'spring' :
95- run_script ('spring_sound.py' )
96- elif ss_new == 'winter' :
97- run_script ('winter_sound.py' )
98- else :
99- pass
100- ss_new = ''
88+ for season , config in SEASONS .items ():
89+ GPIO .remove_event_detect (config ['pin' ])
10190
10291if __name__ == '__main__' :
103- onoff_pin = 9 #physical 21
104- GPIO .setmode (GPIO .BCM )
105- GPIO .setup (onoff_pin , GPIO .IN )
106- target_scripts = ['playsound.py' ,'plant_classification.py' ,'spring_sound.py' ,'rainy_sound.py' ,'winter_sound.py' ]
107- while True :
108- GPIO .wait_for_edge (onoff_pin , GPIO .RISING )
109- print ("ON button pressed. Starting system..." )
110- time .sleep (0.3 )
111- choose_season_thread = choose_season ()
112- choose_season_thread .start ()
113- run_script ('playsound.py' )
114- GPIO .wait_for_edge (onoff_pin , GPIO .RISING )
115- print ("OFF button pressed. Stopping system..." )
116- time .sleep (0.3 )
117- choose_season_thread .stop ()
118- ss_old = ''
119- ss_new = 'spring'
120- kill_python_scripts_by_name (target_scripts )
121-
122- # if __name__ == '__main__':
123- # onoff_pin = 7 # physical 26
124- # GPIO.setmode(GPIO.BCM)
125- # GPIO.setup(onoff_pin, GPIO.IN)
126- # target_scripts = ['playsound.py', 'plant_classification', 'spring_sound.py', 'rainy_sound.py', 'winter_sound.py']
127- # run_script('playsound.py')
128-
129- # while True:
130- # GPIO.wait_for_edge(onoff_pin, GPIO.RISING)
131- # print("ON button pressed. Starting system...")
132- # time.sleep(0.3)
133-
134- # choose_season_thread = choose_season()
135- # choose_season_thread.start()
92+ GPIO .setwarnings (False )
93+ GPIO .setmode (GPIO .BCM )
94+ GPIO .setup (ONOFF_PIN , GPIO .IN )
95+ for season , config in SEASONS .items ():
96+ GPIO .setup (config ['pin' ], GPIO .IN )
13697
137- # try:
138- # while GPIO.input(onoff_pin) == GPIO.LOW:
139- # voltages = read_v()
140- # voltage_str = ' | '.join([f"CH{i}: {v:.2f} V" for i, v in enumerate(voltages)])
141- # print("Voltages ->", voltage_str)
142- # time.sleep(0.5)
143- # except KeyboardInterrupt:
144- # break
98+ target_scripts = ['playsound.py' , 'plant_classification.py' , 'spring_sound.py' , 'rainy_sound.py' , 'winter_sound.py' ]
14599
146- # print("OFF button pressed. Stopping system...")
147- # choose_season_thread.stop()
148- # ss_old = ''
149- # ss_new = 'spring'
150- # kill_python_scripts_by_name(target_scripts)
100+ try :
101+ while True :
102+ GPIO .wait_for_edge (ONOFF_PIN , GPIO .RISING )
103+ logger .info ("ON button pressed. Starting system..." )
104+ time .sleep (0.3 )
105+ choose_season_thread = choose_season ()
106+ choose_season_thread .start ()
107+ run_script ('playsound.py' )
108+ GPIO .wait_for_edge (ONOFF_PIN , GPIO .RISING )
109+ logger .info ("OFF button pressed. Stopping system..." )
110+ time .sleep (0.3 )
111+ choose_season_thread .stop ()
112+ choose_season_thread .join ()
113+ kill_python_scripts_by_name (target_scripts )
114+ finally :
115+ GPIO .cleanup ()
116+ logger .info ("GPIO resources cleaned up." )
0 commit comments