I am developing an application on a raspberry pi with an HC-SR01 sensor. For now I have a python script that checks the water level every second.
import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
GPIO_TRIGGER = 16
GPIO_ECHO = 18
GPIO.setup(GPIO_TRIGGER, GPIO.OUT)
GPIO.setup(GPIO_ECHO, GPIO.IN)
def distance():
# set Trigger High
GPIO.output(GPIO_TRIGGER, True)
# set Trigger after 0.1ms low
time.sleep(0.00001)
GPIO.output(GPIO_TRIGGER, False)
startTime = time.time()
endTime = time.time()
# store start time
while GPIO.input(GPIO_ECHO) == 0:
startTime = time.time()
# store arrival
while GPIO.input(GPIO_ECHO) == 1:
endTime = time.time()
# elapsed time
TimeElapsed = endTime - startTime
# multiply with speed of sound (34300 cm/s)
# and division by two
distance = (TimeElapsed * 34300) / 2
return distance
while True:
dist = distance()
print ("Entfernung = %.1f cm" % dist)
time.sleep(1)
This is working fine and now I want to send an E-Mail to myself then the water level is above a certain limit. The sending of the E-Mail is not the problem but the logic when to send it.
If I put a sendEmail() function in the while loop I would get an E-Mail every second when the water level is reached. So the following wouldn't do it:
#global varaible
alarm_waterlevel = 170
#in while loop
if dist > alarm_waterlevel
sendMail()
So I'm looking for a clever solution to only send the mail once the water level is reached.
I was thinking about a global variable and check if the water level has dropped below a certain point before triggering the mail again. Something like this:
#global varaible
alarm_waterlevel = 170
mail_sent = false
#in while loop
if dist >= alarm_waterlevel && mail_sent == false
sendMail()
mail_sent = true
if dist <= alarm_waterlevel - 10
mail_sent = false
Do you think this is a fault tolerant solution? Any good advices out there to help me get around with this?
Your approach looks reasonable to me, for a Raspberry Pi solution ;)
Another thing that you could think of is adding maybe three different warning levels or states that your system can be in.
L1: send once, 80% full
L2: send every 5 minutes, 90 % full
L3: send every minute, > 100 %
And you have to think of when to change the states. You could add something like a hysteresis. That could be elegant so you don't have to use three different height levels.
If the water level increases you could switch the state, e.g. at 80 % but only switch back to a lower warning state when it is below 70 %. The idea is that your system is not constantly changing states when the water level is between 78 % and 82 %.
Another thing that you could think of is like a projection by looking at the derivative (first or maybe additionally second) of the water level. Then you could warn even earlier when the water level is rising quickly but it is still below a critical threshold.
I think you have a general logic problem in your if statement.
You want to send an e-mail when the distance is greater than or equal to your alert level.
I think your ultrasonic sensor measures from top to bottom, so your if statement should be:
size_water_container = 200 #Just a estimadet value
if dist < (size_water_container - alarm_waterlevel) and waterlevel_before > (size_water_container - alarm_waterlevel):
sendMail()
If i am wrong, than ignore it :)
But now to your Question.
If you permanently query the distance, than you have to permanently save your waterlevel_before, but not in the if statement. Just do it afterwards
alarm_waterlevel = 170
size_water_container = 200
waterlevel_before = 0
while True:
dist = distance()
if dist < (size_water_container - alarm_waterlevel) and waterlevel_before > (size_water_container - alarm_waterlevel):
sendMail()
waterlevel_before = dist
time.sleep(1)
Related
I'm trying to use pyfirmata to use an Arduino Ultrasonic sensor. I used Arduino Uno board and HC-SR04 Ultrasonic sensor. Here is the code I'm using. The code ran smoothly, it's just that it seems the echo pin failed to get an impulse from the trigger ultrasonic sound, so it keeps on getting False (LOW reading) and thus giving me false distance reading. Does anyone have a solution for this problem?
import pyfirmata
import time
board = pyfirmata.Arduino('COM16')
start = 0
end = 0
echo = board.get_pin('d:11:i')
trig = board.get_pin('d:12:o')
LED = board.get_pin('d:13:o')
it = pyfirmata.util.Iterator(board)
it.start()
trig.write(0)
time.sleep(2)
while True:
time.sleep(0.5)
trig.write(1)
time.sleep(0.00001)
trig.write(0)
print(echo.read())
while echo.read() == False:
start = time.time()
while echo.read() == True:
end = time.time()
TimeElapsed = end - start
distance = (TimeElapsed * 34300) / 2
print("Measured Distance = {} cm".format(distance) )
I've tried changing the time.sleep() to several value and it still doesn't work. It works just fine when I'm using Arduino code dirrectly from Arduino IDE.
I haven't done the exact math but given a range of 50cm you're at about 3ms travel time. That would mean you need to turn off the pulse and poll the pin state within that time.
That's not going to happen. The echo probably arrives befor you have turned off the emitter through PyFirmata. You should do the delay measurement on the Arduino.
I solve this false data problem by counting. I observe that false data comes after 2 or 3 sec. So if it takes More than 2 or 3 sec I clear count and restarts it from 0;
Sudo code:
cnt = 0;
if sensorvalue <= 20 && sensorvalue <= 30:
cnt++;
if cnt>=5:
detected = true;
cnt =0;
if cnt<5 && lastDecttime>2 (2 sec):
cnt = 0; // Here we handle the false value and clear the data
I'm currently trying to work this exact problem out. I can get the sensor to work using the Arduino IDE directly, but not with python and pyfirmata. I am getting some output, but its mostly non-sensical.
Here's an example output I'm getting, while keeping the sensor at the same distance from my object:
817.1010613441467
536.828875541687
0.0
546.0820078849792
0.0
0.0
1060.0213408470154
Regarding your code, the only thing I can see that you could do differently is to use the board.pass_time function instead of time.sleep(). Let me know if you get anywhere!
import pyfirmata as pyf
import time
def ultra_test():
board = pyf.Arduino("COM10")
it = pyf.util.Iterator(board)
it.start()
trigpin = board.get_pin("d:7:o")
echopin = board.get_pin("d:8:i")
while True:
trigpin.write(0)
board.pass_time(0.5)
trigpin.write(1)
board.pass_time(0.00001)
trigpin.write(0)
limit_start = time.time()
while echopin.read() != 1:
if time.time() - limit_start > 1:
break
pass
start = time.time()
while echopin.read() != 0:
pass
stop = time.time()
time_elapsed = stop - start
print((time_elapsed) * 34300 / 2)
board.pass_time(1)
I have a python program running a clock and thermometer (raspberry pi + fourletterphat), see program below. I change between showing time or temperature by clicking a single button (gpio16).
What I need help with:
I want to pause the program during night, 21:00 - 06:00, and clear the display because the light is annoying. With the current code I get the display to clear at time for night to start but it does not start again.
If, during above period, the button is clicked I want the program to run for 10 seconds and then stop/clear display. I simply have no idea how to do this, Not even where start.
Is there an elegant way to do this, preferably by just adding something to the existing program. See below.
I have tried various ways of either clearing the display during night time and/or pausing the program until button is hit (but only during the night time, i want the program running during day to show temperature or time).
I have found many versions on finding it time.now is within my range for night but they seem not to be compatible with starting the program as described in point 2 above. (e.g. if time.now < night_end or time.now >= night_start:)
in code below function bright() sets the brightness AND turns off the display at night start 20:00.
Function night() is my feeble start on the restart display at night time but I have not gotten further.
#! /usr/bin/env python3
#Tempclock working with fourletterphat from pimoroni. One switch to change between temp and clock.
#sets brightness at startup but not continuously.
import glob
import time
import datetime
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(16, GPIO.IN, pull_up_down=GPIO.PUD_DOWN )
input_state = GPIO.input(16)
import fourletterphat as flp
# Find 1s temp sensor
base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'
# times for setting brightness of display:
# d2 and d3 time dim and brighten display
d2 = 18
d3 = 7
# d4 and d5 time to turn off display for nigh
d4 = 20
d5 = 6
butt = 1
# Set brightness
def bright():
todays_date = datetime.datetime.now()
hn = (todays_date.hour)
if hn < d5 and hn > d4:
flp.clear()
flp.show()
elif hn < d2 and hn > d3:
flp.set_brightness(12)
else:
flp.set_brightness(0)
# Define nighttime display off
def night():
todays_date = datetime.datetime.now()
hn = (todays_date.hour)
if hn < d5 or hn => d4:
flp.set_brightness(5)
# define temp and time reading
def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines
def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
time.sleep(0.2)
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c, temp_f
def display_temp():
# temp = read_temp()[1] # F
temp = read_temp()[0] # C
flp.print_float(temp, decimal_digits=1, justify_right=True)
flp.show()
def display_time():
now = datetime.datetime.now()
str_time = time.strftime("%H%M")
flp.print_number_str(str_time)
flp.show()
# Display time or temp button on gpio pin 16 push button counter "butt" and set
# brightness bright() according to time of day. Function night() turns on display for 10sec if at night, then turns it off.
while True:
bright()
if GPIO.input(16) == 0:
butt = butt + 1
night()
if butt > 2:
butt = 1
if butt == 1:
display_time()
flp.show()
elif butt == 2:
display_temp()
flp.show()
time.sleep(0.2)
I'll start by assuming that your code to display time and read temperature is working correctly. Also, since I don't have a RaspberryPi with me, I'm focusing on how to organise your logic on the parts that I think you are having difficulty. My code to specifically turn the display on or off may be wrong, but I guess you figured that part out already. Since your question is about an elegant way to do this, the solution is obviously opinion-based. What I am proposing here is a simple but sound way to do it.
The first thing to notice is that you want two modes of operation. I'll call them day_mode and night_mode. The behaviour you expect is quite different for each mode:
day_mode: display is always on, when button 16 is pressed, the display should change from temperature to time. It should be active from 6:00 until 21:00
night_mode: display is normally off. When button 16 is pressed, the display should turn on and display something (you didn't specify, but I'll assume it is the temperature). It should be active when day_mode is not active.
When thinking about modes, it usually helps to separate three things:
What you need to do when you enter that mode of operation (for example, when you enter day_mode you should turn on the display and show the clock, when you enter night_mode you should turn off the display)
What is the behaviour you want during the mode. In your case, it is what we discussed above.
Any clean-up action needed when you exit the mode. Here none is needed, so we will skip this part.
So, let's begin! From your original code, I'll keep everything but the functions night, bright and the final loop. The first thing to do is create a function that tells in which mode we should be:
import datetime as dt
def which_mode():
current_hour = dt.datetime.now().time().hour
# In your code you seemed to need special behaviour between 6 and 7 and 18 and 21,
# but since it was not in your question, I'm not including it here
if 6 <= current_hour <= 21:
return 'day_mode'
else:
return 'night_mode'
To represent the modes, I'll use functions. If your problem were more complex, classes with specific enter, exit and during methods and a full blown state machine implementation would be needed. Since your problem is small, let's use a small solution. Each mode is a function, and the fact that you are entering the mode is informed by a parameter is_entering. The function will also receive a signal indicating that the button was pressed (a rising edge, but change according to your implementation). Before that, we will create a function that displays time or temperature based on a parameter:
def display_what(what):
if what == 0: # Time
display_time()
else: # Temperature
display_temp()
display_info = 0 # 0 means time, 1 means temperature
def day_mode(is_entering=False, button_pressed=False):
global display_info # Not very elegant, but gets the job done
if is_entering:
# Here the display is turned on. I got the values from your `bright` function
flp.set_brightness(12)
flp.clear()
# This is the `during` logic. Note that it will be run right after `entering` the first time
if button_pressed:
display_info = (display_info + 1) % 2 # Cycles between 0 and 1
display_what(display_info) # Update the display accordingly
For the night_mode function, we will need to keep a global variable to record when the button was last pressed. By convention, when it is smaller than 0, it means we are not counting the time (so we do not keep "clearing the display" all the time).
time_of_last_press = -1.0
def night_mode(is_entering=False, button_pressed=False):
if is_entering:
# Setup the dark display
flp.clear()
flp.set_brightness(0)
if button_pressed:
# Record when the button was pressed and display something
time_of_last_press = time.time()
flp.set_brightness(4) # I understood from your code that at night the display is less bright
display_what(1) # Temperature? You can use a similar logic from day_mode to toogle the displays
elif ((time.time() - time_of_last_press) > 10.0) and (time_of_last_press >= 0):
flp.clear()
flp.set_brightness(0)
time_of_last_press = -1.0
Finally, the main loop becomes relatively simple: check what should be the next mode. If it is different from the current one, set is_entering as True. Then call the function for the current mode.
# Setup the GPIO to detect rising edges.
GPIO.add_event_detect(16, GPIO.RISING)
current_mode = None # In the first run, this forces the `entering` code of the correct mode to run
while True:
button_pressed = GPIO.event_detected(16)
next_mode = which_mode()
changed = next_mode != current_mode
current_mode = next_mode
if current_mode == 'day_mode':
day_mode(changed, button_pressed)
else: # There are only two modes, but could be an elif if there were more modes
night_mode(changed, button_pressed)
time.sleep(0.2)
You may have noticed that there is some repetition: for example, in night_mode, the code to clear the display is the same code used when entering it, so you could actually call night_mode(True) to run that code again. Most importantly, there are two ifs that are similar: the one in which_mode and the one in the main loop. This can be fixed (and the code made more generic) if we change which_mode to return the function representing the mode, instead of a string. This may be a little more difficult to read if you are not used to functions returning functions, but it is a very flexible way of doing this sort of state machine:
# Must be defined after the functions `day_mode` and `night_mode` are defined
def which_mode():
current_hour = dt.datetime.now().time().hour
# In your code you seemed to need special behaviour between 6 and 7 and 18 and 21,
# but since it was not in your question, I'm not including it here
if 6 <= current_hour <= 21:
return day_mode # Note that this is not a string. It is a function!
else:
return night_mode
# Setup the GPIO to detect rising edges.
GPIO.add_event_detect(16, GPIO.RISING)
current_mode = None # In the first run, this forces the `entering` code of the correct mode to run
while True:
button_pressed = GPIO.event_detected(16)
next_mode = which_mode()
changed = next_mode != current_mode
current_mode = next_mode
current_mode(changed, button_pressed) # Now current_mode is a reference to one of the two mode functions, so we just need to call it.
time.sleep(0.2)
thank you in advance for reading and taking the time to troubleshoot this with me!
Let me first start off with the important stuff:
Target Device:
Raspberry Pi 4B
Electronic-Salon Relay Hat
Development Environment:
macOS Catalina
PyCharm
Python 2.7.10
At my home I have a spring that serves my home with water. The solution I came up with to prevent dirty water caused by bad rainy weather loosening up the ground soil from entering my cistern is closing a valve and waiting for about 12 hours for the water to clear back up. Then I open the valve and clear water flows into my cistern providing my home with water, and that solution works really well.
I recently came up with the conclusion that I want to automate this process with a normally open solenoid. I purchased a Raspberry Pi, a Relay Hat, and an Ambient Weather weather station.
What I'm looking to do with Python 2.7.10 is check the same variable against itself after an allotted time. In this example, I'm checking the relative barometric pressure against itself and I'm wanting to look for a significant negative change in that variable.
i.e "What does variable A have? Okay, now wait 3 seconds. What does A have now? How much has it changed?"
This is what I've bodged together so far, how can I improve? Thank you.
At first I was thinking maybe I should plot a chart with the data and compare the difference between the two plot points, but I wasn't sure how to use Matplotlib.
# This is the executing script for the Smart Valve.
# This project is powered by raspberry pi and 120 angry pixies.
import time,imp
from ambient_api.ambientapi import AmbientAPI
# This code will pull the data from the weather computer
api = AmbientAPI()
devices = api.get_devices()
device = devices[0]
time.sleep(1) #pause for a second to avoid API limits
# The code below is meant for telling the python interpreter to behave normally whether or not it's in a RPi env or a
# developer env
try:
imp.find_module('RPi.GPIO')
import RPi.GPIO as GPIO
except ImportError:
"""
import FakeRPi.GPIO as GPIO
OR
import FakeRPi.RPiO as RPiO
"""
import FakeRPi.GPIO as GPIO
# this code compares the rate of change for the barometric pressure over time and checks if rate is negative
a1 = None
a2 = None
while True:
weatherData = device.get_data()
data = dict(weatherData[0])
pressure = data[u'baromrelin']
wind = data[u'windspeedmph']
rain = data[u'hourlyrainin']
a1 = pressure
time.sleep(30)
a2 = pressure
print("A1 is equal to " + str(a1))
print("A2 is equal to " + str(a2))
if a1 > a2:
print("we should close the valve, it'll rain soon")
continue
elif a1 == a2:
print("It's all hunky dory up here!")
break
Firstly i would avoid Python 2. 2020 was the last year to it. Now Python 3 all the way. Also you won't need u'text' as everything is unicode.
The problem with your code is that you reference the same pressure variable twice.
Here's why. You do nothing to change it after the wait. Its effectively:
pressure = 123.4
first = pressure # 123.4
thread.sleep(30)
second = pressure # 123.4
second == first
# True
I would avoid setting two measurements in a single. And I'm going to use previous and current as names as it makes more sense than a1 or first.
Without a loop.
prev = None
# Pass 1
weatherData = device.get_data()
data = dict(weatherData[0])
pressure = data[u'baromrelin']
curr = pressure # e.g. 123.4
if prev is None or curr > prev:
print("Pressure is rising!")
# Current iteration's current pressure will become the next iteration's previous pressure. You could even then see curr = None because we about to overwrite on next iteration.
prev = curr # 123.4
time.sleep(30)
# Pass 2
weatherData = device.get_data()
data = dict(weatherData[0])
pressure = data[u'baromrelin']
curr = pressure # e.g. 234.5
if prev is None or curr > prev:
print("Pressure is rising!")
# 234.5 > 123.4
time.sleep(30)
With a loop:
WAIT = 30
prev = None
while True:
weatherData = device.get_data()
data = dict(weatherData[0])
pressure = data[u'baromrelin']
curr = pressure
if prev is None or curr > prev:
print("Pressure is rising!")
break
print(f"Nothing usual. Waiting {WAIT} seconds...")
time.sleep(WAIT)
The general design pattern for this is:
oldval = 0
while 1:
newval = read_the_value()
if oldval != newval:
take_action()
oldval = newval
sleep(xxx)
I think that will work for you.
I have raspberry pi model 3b+ with a HC-SR04 ultrasonic distance sensor (there is also a couple of ds18b20 and a DHT21 but I think they're unrelated to my problem).
I have found a python script to make measurements from it and modified it to my needs - mostly to take a couple of reading spanned in time, take an average from it and map the value to range from 0 to 100, as the percentage and commit it to the influx database for grafana and domoticz.
The code:
#source: https://tutorials-raspberrypi.com/raspberry-pi-ultrasonic-sensor-hc-sr04/
#Libraries
import RPi.GPIO as GPIO
import time
from influxdb import InfluxDBClient
import sys
# https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Python
import requests
client = InfluxDBClient(database='pellet')
series = []
#GPIO Mode (BOARD / BCM)
GPIO.setmode(GPIO.BCM)
#set GPIO Pins
GPIO_TRIGGER = 23
GPIO_ECHO = 22
#set GPIO direction (IN / OUT)
GPIO.setup(GPIO_TRIGGER, GPIO.OUT)
GPIO.setup(GPIO_ECHO, GPIO.IN)
def distance():
# set Trigger to HIGH
GPIO.output(GPIO_TRIGGER, True)
# set Trigger after 0.01ms to LOW
time.sleep(0.00001)
GPIO.output(GPIO_TRIGGER, False)
StartTime = time.time()
StopTime = time.time()
# save StartTime
while GPIO.input(GPIO_ECHO) == 0:
StartTime = time.time()
# save time of arrival
while GPIO.input(GPIO_ECHO) == 1:
StopTime = time.time()
# time difference between start and arrival
TimeElapsed = StopTime - StartTime
# multiply with the sonic speed (34300 cm/s)
# and divide by 2, because there and back
distance = (TimeElapsed * 34300) / 2
return distance
def pellet(dist):
# zmierzona odleglosc
# dist = distance()
# do zmierzenia poziom maksymalny
# 63 - do pokrywy
in_min = 63
# do zmierzenia poziom minimalny
in_max = in_min + 100
#wyjscie jako procent, od 0 do 100
out_min = 100
out_max = 0
# map z arduino: https://www.arduino.cc/reference/en/language/functions/math/map/
return (dist - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
def loop():
# nie wiecej jak 200 iteracji
loop = 200
# suma
total = 0
# tabelka z pojedynczmi wynikami
measurements = []
# liczba pomiarow do zrobienia
counter = 10
counter1 = 0
# czas pomiedzy pomiarami
sleep =30
#
while loop > 0:
loop -= 1
time.sleep(sleep)
# koniec, jesli wykonano liczbe pomiarow
if counter == 0:
#print(total/10)
return pellet(total/10), measurements
break
if loop == 0 and counter1 != 0:
return pellet(total/counter1), measurements
break
if loop == 0 and (counter1 == 0 or total == 0):
GPIO.cleanup()
sys.exit()
dist = distance()
# jesli wynik jest zly
if dist < 63 or dist > 163:
print("nie ok")
continue
counter -= 1
measurements.append(dist)
counter1 += 1
total += dist
print("total po ",counter1 , "sek: ", total, "dist: ", dist)
print(total/10)
#return total/10
if __name__ == '__main__':
try:
#dist = distance()
#print ("Measured Distance = %.1f cm" % dist)
#print (pellet(dist))
loop=loop()
print("avg :", loop[0])
#print("measurs :", loop[1])
#print("test :", loop[1][2])
if (1):
point = {
"measurement": "pellet",
"tags": {
"location": "piwnica",
"type": "hc-sr04"
},
"fields": {
"value": loop[0],
"raw_measurement1": loop[1][0],
"raw_measurement2": loop[1][1],
"raw_measurement3": loop[1][2],
"raw_measurement4": loop[1][3],
"raw_measurement5": loop[1][4],
"raw_measurement6": loop[1][5],
"raw_measurement7": loop[1][6],
"raw_measurement8": loop[1][7],
"raw_measurement9": loop[1][8],
"raw_measurement10": loop[1][9]
}
}
series.append(point)
client.write_points(series)
url = 'http://localhost:8080/json.htm?type=command¶m=udevice&nvalue=0&idx=13&svalue='+str(loop[0])
r = requests.get(url)
GPIO.cleanup()
# Reset by pressing CTRL + C
except KeyboardInterrupt:
print("Measurement stopped by User")
GPIO.cleanup()
The problem is I noticed that the CPU temperature graph was elevated, with many short valleys to the about correct temperature.
When I ssh'd to the pi and run htop I saw that it was this script that is using 100% cpu.
But the weirdest thing is that the script is running in crontab every 15 minutes since yesterday, from about 14:30 and raise CPU temp started today at around 11:00.
I'm not a developer or a programmer and I just mostly copied the code from around the web so I don't know if this is some part of the code that did this (but why after 21 hours?) or what and why, and how to debug and fix it.
so it isn't just enviromental thing as the pi is in the attic where is about 5C to 10C.
Thank you for your help.
Here:
while GPIO.input(GPIO_ECHO) == 0:
StartTime = time.time()
this says "if the pin is 0, save the time, if the pin is zero, save the time, if the pin...." incessantly. You'll want to wait a little time after each check
while GPIO.input(GPIO_ECHO) == 0:
time.sleep(0.001) # 1 ms
StartTime = time.time()
The check itself probably takes ~us, so this will reduce CPU usage by 99%. You might want to do the same for the pin==1 case, depending on how accurate you need the times to be.
While it is impossible to know for sure where the issue lies without debugging directly on your system, and a glance at the code reveals several possible bugs in the logic, the one place that is most likely to cause the issue is the distance function.
As #mdurant already pointed out, your read loops will jump the CPU usage to 100%, but I suspect there is also another issue:
The trigger code and the read code are time sensitive!
The problem is, we don't know how much time actually passes between
# set Trigger to HIGH
GPIO.output(GPIO_TRIGGER, True)
# set Trigger after 0.01ms to LOW
time.sleep(0.00001)
GPIO.output(GPIO_TRIGGER, False)
and:
# save StartTime
while GPIO.input(GPIO_ECHO) == 0:
StartTime = time.time()
# save time of arrival
while GPIO.input(GPIO_ECHO) == 1:
StopTime = time.time()
While this simple algorithm - pulse trigger, count return interval will work on a microcontroller like Arduino, it is not reliable on a full blown computer like Raspberry Pi.
Microcontrollers run a single thread, with no OS or task scheduling, so they run code in real time or as close to it as possible (borrowing a few interrupts here and there).
But in your case you are running an interpreted language on a multitasking operating system, without explicitly giving it any high priority.
This means, your process could be suspended just enough time to miss the return "ping" and get stuck in the first loop.
This may only happen rarely when something else puts a load on the Pi, which would explain why you only noticed the issue after 21 hours running.
You should implement some form of timeout for GPIO reading loops and return an error value from the distance function if that timeout is reached to ensure you do not have an infinite loop when something goes wrong with the hardware, or you miss the return ping due to scheduling issues.
I suggest something like this:
def distance():
MAX_READ_ATTEMPTS = 10000 #this is a random value I chose. You will need to fine-tune it based on actual hardware performance
# set Trigger to HIGH
GPIO.output(GPIO_TRIGGER, True)
# set Trigger after 0.01ms to LOW
time.sleep(0.00001)
GPIO.output(GPIO_TRIGGER, False)
# lets not have any unneeded code here, just in case
# save StartTime
while GPIO.input(GPIO_ECHO) == 0 and retry_counter < MAX_READ_ATTEMPTS:
retry_counter += 1
StartTime = time.time()
# maximum number of retries reached, returning with error condition
if retry_counter == MAX_READ_ATTEMPTS:
return -1
reatry_counter = 0
# save time of arrival
while GPIO.input(GPIO_ECHO) == 1 and retry_counter < MAX_READ_ATTEMPTS:
retry_counter += 1
StopTime = time.time()
# maximum number of retries reached, returning with error condition
if retry_counter == MAX_READ_ATTEMPTS:
return -1
# time difference between start and arrival
TimeElapsed = StopTime - StartTime
# multiply with the sonic speed (34300 cm/s)
# and divide by 2, because there and back
distance = (TimeElapsed * 34300) / 2
return distance
I am intentionally not adding a delay between reads, because I am not sure what the tolerance for this measurement is in terms of timing, and sleep functions can't guarantee an exact delay (again, due to OS / CPU scheduling).
A brief 100% CPU load should be worth it to ensure accurate and valid measurements, as long as it is not kept up for too long, which our retry counting method should prevent.
Note that my only experience with ultrasonic sensors is using Arduino where a special pulseIn function is used that takes care of the implementation details of measurement so this solution is mostly an educated guess and I have no way of testing it.
enter image description here
I am making serial port communication device from a genious guy's work instructables.com.
It will measure the distance of the hamster's running in a day or month.
Using 4, 6 pin of the serial port cable, if the hamster runs, the device can count the numbers how many time did she run.
When I run the py file with Python27 like below, some errors occure.
"python hamster-serial.py progress.txt"
I cannot understand what's going on.
I am using windows8 and Python2.7 version.
Could you check my source, please?
import datetime
import serial
import sys
# Check for commandline argument. The first argument is the the name of the program.
if len(sys.argv) < 2:
print "Usage: python %s [Out File]" % sys.argv[0]
exit()
# Open the serial port we'll use the pins on and the file we'll write to.
ser = serial.Serial("/dev/ttyS1")
# Open the file we're going to write the results to.
f = open(sys.argv[1], 'a')
# Bring DTR to 1. This will be shorted to DSR when the switch is activated as the wheel turns.
ser.setDTR(1)
# The circumferance of the wheel.
circ = 0.000396 # miles
# Total distance traveled in this run of the program.
distance = 0.0
print "%s] Starting logging." % datetime.datetime.now()
start = datetime.datetime.now()
# This function a period of the wheel to a speed of the hamster.
def toSpeed(period):
global circ
seconds = period.days * 24 * 60 * 60 + period.seconds + period.microseconds / 1000000.
return circ / (seconds / 60. / 60.)
# Waits for the DSR pin on the serial port to turn off. This indicates that the
# switch has turned off and the magnet is no longer over the switch.
def waitForPinOff():
while ser.getDSR() == 1:
1 # Don't do anything while we wait.
# Waits for the DSR pin on the serial port to turn on. This indicates that the
# switch has turned on and the magnet is current over the switch.
def waitForPinOn():
while ser.getDSR() == 0:
1 # Don't do anything while we wait.
# The main loop of the program.
while 1:
waitForPinOn()
# Calculate the speed.
end = datetime.datetime.now()
period = end - start
start = end
speed = toSpeed(period)
# Increment the distance.
distance = distance + circ
waitForPinOff()
# We'll calculate the time the switch was held on too so but this isn't too useful.
hold = datetime.datetime.now() - start
# If the switch bounces or the hamster doesn't make a full revolution then
# it might seem like the hamster is running really fast. If the speed is
# more than 4 mph then ignore it, because the hamster can't run that fast.
if speed < 4.0:
# Print out our speed and distance for this session.
print "%s] Distance: %.4f miles Speed: %.2f mph" % (datetime.datetime.now(), distance, speed)
# Log it to and flush the file so it actually gets written.
f.write("%s\t%.2f\n" % (datetime.datetime.now().strftime("%D %T"), speed))
f.flush()
Well, ser = serial.Serial("/dev/ttyS1") is for a linux machine, on windows you'll need something like ser = serial.Serial("COM1") (you can check what COM do you need in the device manager).
As a side note,
def waitForPinOff():
while ser.getDSR() == 1:
1 # Don't do anything while we wait.
Will eat you CPU. You are better of with:
def waitForPinOff():
while ser.getDSR() == 1:
time.sleep(1) # Don't do anything while we wait.