Run once until conditions change - python

What I'm trying to accomplish: if the room lights go off, the monitor should dim. If the room lights go back on, the monitor should get brighter.
On a Rpi4B, I'm trying to use ddcutil in combination with a photoresistor and capacitor to automatically adjust monitor brightness depending on ambient light in the room. I really only need 3 brightness settings; one each for bright, average, and dim light conditions.
I've been editing some code I found and I have the code responding to three different levels of brightness (just text verification telling me the if the light is dim, medium, or bright.) The issue is that it keeps printing the result over and over and I only need it to print (or eventually run my ddcutil routine) once, but STILL continue to check the ambient light level and react accordingly. How do I continually check input from the sensor without continually passing an instruction to ddcutil? I know very little about writing code and I suspect a Boolean flag may be the answer that I don't understand. Thanks for any help.
#!/usr/local/bin/python
import RPi.GPIO as GPIO
import time
__author__ = 'Gus (Adapted from Adafruit)'
__license__ = "GPL"
__maintainer__ = "pimylifeup.com"
GPIO.setmode(GPIO.BOARD)
#define the pin that goes to the circuit
pin_to_circuit = 3
def rc_time (pin_to_circuit):
count = 0
#Output on the pin for
GPIO.setup(pin_to_circuit, GPIO.OUT)
GPIO.output(pin_to_circuit, GPIO.LOW)
time.sleep(.2)
#Change the pin back to input
GPIO.setup(pin_to_circuit, GPIO.IN)
#Count until the pin goes high
while (GPIO.input(pin_to_circuit) == GPIO.LOW):
count += 1
return count
#Catch when script is interupted, cleanup correctly
try:
# Main loop
while True:
if(rc_time(pin_to_circuit)) > 4000:
print("Too dark. Frame off.")
if(rc_time(pin_to_circuit)) < 4000 and (rc_time(pin_to_circuit)) > 1000:
print("Good light. Medium brightness.")
if(rc_time(pin_to_circuit)) < 1000:
print("Bright day. Maximum brightness set.")
except KeyboardInterrupt:
pass
finally:
GPIO.cleanup()

I would store the last light value in a variable and in your loop compare that to the current value. I would also move the checking logic to it's own method so it can be re-used elsewhere if required.
def get_light_value():
pin_rc_time = rc_time(pin_to_circuit)
if pin_rc_time > 4000:
return "low"
elif pin_rc_time <= 4000 and pin_rc_time > 1000:
return "medium"
else:
return "high"
Notice here that we also only call rc_time once per loop. That will avoid any weirdness if the value changes while the conditions are being determined. I changed the logic to check for <= 4000 which will cover the missing condition in your code where the value is exactly 4000. And I also used elif/else to ensure only one branch is evaluated.
Using this in the main loop:
last_light_value = None
curr_light_value = None
while True:
last_light_value = curr_light_value
curr_light_value = get_light_value()
# Do nothing if the value did not change
if last_light_value == curr_light_value:
time.sleep(1)
continue
if curr_light_value == "low":
print("Too dark. Frame off.")
elif curr_light_value == "medium":
print("Good light. Medium brightness.")
else: # Value must be high.
print("Bright day. Maximum brightness set.")
Homework for you:
To take this further, you might also want to think about what will happen if the pin value is very close to the threshold of the cutoffs. This will most likely cause the value to flip back and between categories, as some sensors are not extremely precise, and things like florescent lighting can oscillate in intensity.
To combat this, you could implement a filter that to only change the value if it is in a different category and has at least changed some delta of value since the last check. You can use a similar trick to the while loop which takes into account the last value seen before making a determination on if a value should be changed.

Related

Infinite while loop in python - with conflicting conditions

my typo error led in the 'while loop' function led me to the following question
from time import sleep
from random import randint
def control_pressure():
pressure = measure_pressure()
while True:
if pressure <= 500:
break
while pressure > 500 and pressure <= 700:
run_standard_safeties()
pressure = measure_pressure()
while pressure > 100: # Here is the typo
run_critical_safeties()
pressure = measure_pressure()
print("Wow! The system is safe...")
def measure_pressure():
pressure = randint(450, 800)
print(f"psi={pressure}", end="; ")
return pressure
def run_standard_safeties():
print("Running standard safeties...")
sleep(0.2)
def run_critical_safeties():
print("Running critical safeties...")
sleep(0.7)
if __name__ == "__main__":
control_pressure()
In the above, I made a typo and checked pressure > 100, while condition. But earlier in the first part of the function, it checked, while pressure <= 500, then break. So technically any value lesser than or equal to 500 should return True and break the function without reaching the pressure > 100 condition.
But this function continued without stopping. Why does it happen?
Thanks
Your scenario is best described sequentially.
Any initial pressure greater than 500 will bypass the break and continue to the first inner loop. Since you're getting a random value 450..800 (a range of 350 - ranges here may be off by one but they're close enough so as not to matter), this has a probability of about 300/350 (the numerator comes from the 500..800 range) or 85%.
The first inner loop will then run as long as you're getting pressures in the range 500..700, a probability of about 200/350, or 57%.
At some point you'll exit that first inner loop and enter the second. And you will enter it because all values in the range 450..800, that you can possibly get as a pressure, are greater than a hundred.
That means, of course, that you will also never exit that loop because no pressure you ever get after that point will be a hundred or less, which is what's needed to exit the loop.
As an aside, you could actually avoid this loops-within-loops structure by realising that you to get the pressure every iteration of every loop, so that can be made common.
Assuming your typo was to use 100 where you meant 700 (as that marches with mutually exclusive ranges), that can be simplified to:
def control_pressure():
while True:
pressure = measure_pressure()
if pressure <= 500:
print("Wow! The system is safe...")
return
if pressure <= 700:
run_standard_safeties()
else:
run_critical_safeties()
Or, if you want to use the := walrus operator(a) (available in Python 3.8+, I think), you could shorten it even further by getting and checking pressure as part of the while statement:
def control_pressure():
while (pressure := measure_pressure()) > 500:
if pressure <= 700:
run_standard_safeties()
else:
run_critical_safeties()
print("Wow! The system is safe...")
(a) So called because, if you rotate it 90° clockwise and squint, it looks like a walrus with its two eyes and two huge tusks.
It basically (in this case) lets you assign a value to a variable as part of checking said variable, so that the value is available for later checks as well.
If the initial number is above 100 and above 500 it will enter the second loop and never stop since it will always be above 100, since your are taking one at random from 450-800.

How do I subtract a number with an if-statement?

This is my code:
import sys
import click
def Seats():
global seats
seats = 168
print("\nThe current seating capacity is {}\n".format(seats))
def Restart():
while True:
restart = click.confirm('\nWould you like to subtract 1 from seats?',
default=True)
if restart == True:
x = seats - 1
Seats()
elif restart == False:
print("\nOk, see you next time!\n")
sys.exit()
Seats()
Restart()
I want to ask the user "Would you like to subtract 1 from seats?" and then subtract one from seats variable and then repeat until the users inputs no.
As pointed in the comments, the main problem in your code is here:
def Seats():
global seats
seats = 168
print("\nThe current seating capacity is {}\n".format(seats))
Whenever and wherever you call the function Seats in your code, the value of seats is redefined because seats = 168 gets executed.
With the problem pointed out, I tried to edit your code maintaining the same structure in order to try to show you how to fix the problem, but changed my mind. The code, despite small, has an unnecessarily complex logic and a far from clean syntax. You are a beginner, so there is no problem with that! It is just that, because of that, I thought that showing you another way to implement this algorithm would be more valuable. Here it goes:
import click
seats = 168
while seats > 0:
subtract_seat = click.confirm('\nWould you like to subtract 1 from seats?', default = True)
if subtract_seat:
seats -= 1
print("\nThe current seating capacity is {}\n".format(seats))
else:
break
print("\nOk, see you next time!\n")
Do not need to import the sys library. You are not using it
while seats > 0 avoids subtracting seats when there are no more seats left and is also the main loop used to interact with the user
The else clause will automatically take care if the first condition is not met
There is absolutely no need for functions or global variables in your code. They might be needed if you insert this code in a bigger context, but keep simplicity in mind
#AshidoBestGirl
To get things to work you have to declare seats to be a global variable in the Restart() function and stop resetting it in the Seats() function. It doesn't have to be declared in the version of the Seats() function below because it no longer tries to set or change its value.
To subtract a number from a variable and store the result back into the variable, you need to either do variable = variable - number or use the more succinct variable -= number statement.
Also, when you want to terminate the while loop in Restart() and let it "fall of the end" and return, all you need to do is break out of the while True loop.
Here's your code with those changes:
import click
seats = 168 # Initialize global variable.
def Seats():
print("\nThe current seating capacity is {}\n".format(seats))
def Restart():
global seats
while True:
restart = click.confirm('\nWould you like to subtract 1 from seats?',
default=True)
if restart:
seats -= 1
Seats()
else:
print("\nOk, see you next time!\n")
break
Seats()
Restart()

How to update polygon size with cumulative key press in Psychopy

I am using Psychopy to create a psychological task. For a given routine, I would like the height of a polygon (rectangle) to increase with every key press (same key every time), until it reaches the maximum number of key presses (e.g. 10). I cannot figure out how to create a loop to count number of key presses within the same routine, nor how to use this to generate a variable that will constantly update the size of the polygon.
Here is what I have tried as code in the routine which gets stuck in while loop... and I am not sure if the loop should go in the code "Before Routine" or for "Each Frame"
total_key_count = 0
while True:
resp_key = event.waitKeys(keyList=['1'])
if resp_key == '1':
total_key_count = total_key_count + 1
# .. or break out of the loop if reach 10
elif total_key_count == 10:
break
Thanks!
Never use event.waitKeys() in a Builder code component. Builder is structured around a drawing loop that requires updating the screen and responding to events on every screen refresh. If you call waitKeys(), you pause execution completely until a key is pressed, which will completely break Builder's temporal structure.
In the Begin routine tab, put this:
key_count = 0
max_keys = 10
In the Each frame tab, put this:
key_press = event.getKeys('1')
if key_press: # i.e. if list not empty
key_count = key_count + 1
if key_count <= max_keys:
# increment the height of the stimulus by some value
# (use what is appropriate to its units):
your_stimulus.size[1] = your_stimulus.size[1] + 0.1
else:
# terminate the routine (if required)
continueRoutine = False
Note that getKeys(), unlike waitKeys() just checks instantaneously for keypresses. i.e. it doesn't pause, waiting for a key. This is fine though, as this code will run on every screen refresh, until the required number of keys have been pushed.
Presumably you also need to save some data about the response. This would best be done in the End routine tab, e.g.
thisExp.addData('completion_time', t) # or whatever needs recording

python loop only print values once

Below is a short piece of python code.. it's for a sensor reading light or darkness. What I want it to do when the value drops below 500 print "light" and when it's above 500 print "Dark". This pretty much works but the text is repeated for every reading.. while I only want it to be printed upon a change.. anyone has any idea how to do this? I did quite a bit of shell programming.. but somehow this simple issue I can't get it done in python..
#!/usr/bin/python
import RPi.GPIO as GPIO, time
GPIO.setmode(GPIO.BCM)
# Define function to measure charge time
def RCtime (PiPin):
measurement = 0
# Discharge capacitor
GPIO.setup(PiPin, GPIO.OUT)
GPIO.output(PiPin, GPIO.LOW)
time.sleep(0.1)
GPIO.setup(PiPin, GPIO.IN)
# Count loops until voltage across
# capacitor reads high on GPIO
last = 9
while (GPIO.input(PiPin) == GPIO.LOW):
measurement += 1
if measurement < 500:
print last
if last == 0:
print "light"
last = 1
if measurement >500:
print "dark"
last = 0
print last
return measurement
# Main program loop
while True:
print RCtime(4) # Measure timing using GPIO4
I think you have the idea right in the if measurement < 500: condition, printing "light" only when last was different. You just have to repeat similar logic in the > 500 condition. But the real problem is that last here is a local variable, so the value will get reset to 9 on every call. So you need to remove the last=9, and define last outside of the function and declare it as global inside the function:
#in main program
last = 9
def RCTime ...:
global last
....
Since you're returning value on each function execution I think the best idea would be to compare it in the while loop and keep the function only for getting the data.
Something along these lines:
previous, current = None, None
def RCtime (PiPin):
[...]
while True:
measurement = RCtime(4)
previous, current = current, measurement<500
if current != previous:
# value changed...
# `current` is bool and True if there is light.
# or you can just deal with the value itself for logic
print measurement
Otherwise returning the value of last and then passing it on when function is next time called is also an acceptable solution.
I simulate the sensor's readings using a list named readings, the rest of the code can be directly translated to your use case
readings = [ 600, 600, 550, 501, 450, 400, 400, 460, 520, 600, 600]
dark = None
for i, reading in enumerate(readings):
dark_now = 'Dark' if reading < 500 else 'Light'
if dark_now != dark:
dark = dark_now
print i, reading, dark
Output
0 600 Light
4 450 Dark
8 520 Light

recurring notification statement after checking a condition in python

I am working on a small proof of concept and using python to illustrate the idea. The idea is the program will run in a loop and will check for input. Now if the input falls under a threshold then it sends a notification. But I am trying to restrict the notification at an interval of 4 sec. And thats where I am loosing either with the logic or with some syntax. Either way It is doing some unexpected things
1: keep on entering 0 and it will display the below threshold message until it reaches a 4 sec mark and then it just prints out the message 4 times in a single line. I want them to show after every 4 seconds. The idea is (A)the input might change in that 4 sec and the notification switches. (B)I want the notification to play out as a reminder with a recurrence of 4 sec every time the script hits the condition if weightIn < 0.5..if it is true then the notification goes out after 4 sec from the first time it was sent
Sorry if I tried over explaining it. I am pretty new to python
import threading
def main():
while True:
weightIn = float(input("Get value: "))
threshold = .5
def operation():
if weightIn < 0.5:
#send notification at an interval of 4 sec
threading.Timer(4.0, operation).start()
print("Below weight threshhold...send notification")
else:
print("You are good")
if threshold is not None:
operation()
main()
First avoid declaring functions in a loop. Then ask yourself, if an object would not be appropriate, because it properly encloses state attributes.
But for the algorithmic part, it is simple (if I have correctly understood the problem ...). Store the timestamp of last notification and send a new one if more the 4 seconds have elapsed. In pseudo-code :
last_notification_time = 0
threshold = 0.5
loop:
weighIn = get_new_value()
if weightIn < threshold:
time = get_time_in_seconds()
if (time > last_notification_time + 4):
last_notification_time = time
send_notification()
# actual processing
In Python, it could look like :
#import time
def main():
last_notification_time = 0
threshold = 0.5
while True:
weighIn = float(input("Get value: "))
if weightIn < threshold:
cur_time = time.time()
if (cur_time > last_notification_time + 4):
last_notification_time = time
print("Below weight threshhold...send notification")
# actual processing
main()

Categories

Resources