python loop only print values once - python

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

Related

Run once until conditions change

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.

- Python Issue: Working with an Array key-value inside a While statement, is overwritting the content even if different names are detected

Explanation:
I'm trying to do a Progress bar, where you can control it by updating some values on an Excel, and it shows the changes live in a .svg file.
Issue:
I've a loop that can repeat itself sometimes, but the names of the Listed key-value inside the Array are changed between 2 options.
The issue is that, even if I've those 2 options separately, and even with the Print showing the values correctly, the data stored inside is in the same key-value as the 1st time the Loop entered the method.
Here is where I declare the ArrayList, among some others properties:
class RangeBar(GraphicsPort):
_opacity = {
"firstPhase": 1,
"thirdPhase": 1
}
Some code of the main() where the While statement is working:
if (rb._empty["firstPhase"] == False):
print("Start Phase 1 Loop")
rb.partialPhase(False, "firstPhase")
print("Exit Phase 1 Loop")
if (rb._empty["fillPhase"] == False):
print("Start Phase 2 Loop")
rb.fillPhase(False, "fillPhase")
print("Exit Phase 2 Loop")
if (rb._empty["thirdPhase"] == False):
print("Start Phase 3 Loop")
rb.partialPhase(False, "thirdPhase")
print("Exit Phase 3 Loop")
Code of the partialPhase method (the one where this happens):
def partialPhase(self, baseCellModified, phase):
if (baseCellModified != False):
self.setValue(baseCellModified)
self._opacity[phase] = baseCellModified / 100
self.sheet_action("update_sheet_data",cell="C3",number=100)
if (phase == "firstPhase"):
self.setValue(86.9)
if (phase == "thirdPhase"):
self.setValue(12.9)
print("New opacity for 'phase':",phase,"from: 'self._opacity[phase]':", self._opacity[phase])
self.sheet_action("opacity_update", opacity=str(self._opacity[phase]))
self.sheet_action("update_sheet_data",cell="D3",number=self.getValue())
svg = str(self.__utils)
text_file = open("./1.svg", "w")
text_file.write(svg)
text_file.close()
timeToAction = self.sheet_action("get_sheet_data", type="numb", cell="F3") * 60
time.sleep(timeToAction)
while self._opacity[phase] > 0:
timeToAction = self.sheet_action("get_sheet_data", type="numb", cell="F3") * 60
changeCell = self.sheet_action("get_sheet_data", type="numb", cell="E3")
action = self.identifyAction("G3")
if (action == "decrement"):
self._opacity[phase] -= (changeCell / 8)
print("'phase' is showing opacity of:", phase)
print("'self._opacity[phase]' is showing:", self._opacity[phase])
if (action == "increment"):
self._opacity[phase] += (changeCell / 8)
if self._opacity[phase] < 0:
print("Entering opacity minor to 0")
print("'phase' is showing opacity of:", phase)
print("'self._opacity[phase]' is showing:", self._opacity[phase])
self._opacity[phase] = 0
self._empty[phase] = True
if (phase == "thirdPhase"):
self._critical = True
self.sheet_action("opacity_update", opacity=str(self._opacity[phase]))
self.sheet_action("update_sheet_data",cell="D3",number=self.getValue())
svg = str(self.__utils)
text_file = open("./1.svg", "w")
text_file.write(str(self.__utils))
text_file.close()
time.sleep(timeToAction)
.svg to start from:
some prints from 1st phase:
After setting it to 0
After exiting Phase 2 (fill empty)
Now it happens the error, the Opacity of "firstPhase" turned 1, and it looks like this:
And now the code interacts with the 'firstPhase' all time, even if 'thirdPhase' appears in the prints, as the 'phase' it's getting this value.
Until it disappears again:
Thanks for take your time to read me, and sorry for the large text 🙌🏼.
At the end of the while loop declare _opacity = {}, we need to make that empty, else for each run it adds the value in it.
I did a Newbie miss that I didn't even noticed.
The code I passed was good, but a method I was calling, was hardcoded from previous tests, and I didn't noticed it.
The phase which I wanted to change, was "thirdPhase" all the time, so it was constantly changing the opacity of the thirdPhase, even if I passed a different opacity, the phase remained the same.
I'm sorry if I wasted someone time on this 🙌🏼 and thanks for the attention.

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.

Execute if statement only once in a period of time in Python?

Creating a python script which receives data from an arduino distance sensor. I am receiving a value each nanosecond. Whenever this value is higher than 50, I want to warn the user. (will eventually do this with a notification program, but for now I am just printing warning). I have the following:
while 1: # Keep receiving data
data = ser.readline() # Getting data from arduino
value = [int(s) for s in data.split() if s.isdigit()] # Converting the Arduino ouput to only get the integer value
if value: # Avoid empty values
distance = value[0] # List to int
if distance > 50: # If bigger than 50 cm, warn user.
warn_user()
I only want to execute the warn_user() function once in 30 seconds, after that, the if statement shouldn't trigger anymore, only when the values drop under 50 and THEN > 50 again. I tried working with True/False statements, timer sleeps but this did not work. Any tips? thanks.
You just have to add some more logical conditions to control the program's flow. Something like this would work:
from time import time
warning_enabled = True
time_of_warning = 0
while 1:
data = ser.readline()
value = [int(s) for s in data.split() if s.isdigit()]
if value:
distance = value[0]
if distance > 50 and warning_enabled:
warn_user()
warning_enabled = False
time_of_warning = time()
if time() - time_of_warning > 30 and not warning_enabled and distance < 50:
warning_enabled = True
What this does is that it keeps track of the last time when the warning was fired and uses the warning_enable flag to make the second if only fire when possible.
Cheers
You need only track what you're looking for to accomplish your goal: the timestamp of the last warning and whether the distance was below the value you're tracking.
import time
distance_was_safe = True # tracks if distance went below specified range
last_warned = 0 # tracks timestamp since last warning
safe_distance = 50 # 50 cm
warn_interval = 30 # 30 sec, warn no more than once every interval
while True:
# Get data from Arduino.
data = ser.readline()
# Convert the Arduino output to only get the integer values.
value = [int(s) for s in data.split() if s.isdigit()]
# Avoid empty output.
if value:
# Get the first integer.
distance = value[0]
# If greater than safe zone, potentially warn the user.
if distance > safe_distance:
# Warn the user if distance was below range,
# and it has been enough time since the last warning.
if distance_was_safe and time.time() - last_warned > warn_interval:
distance_was_safe = False
last_warned = time.time()
warn_user()
else:
# Distance was less than warning distance, reset the flag.
distance_was_safe = True

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