How to work with delay between serial writing and reading - python

My python script is working with a modem, and the first step of the process is to check if there is enough signal. Through AT commands,I can ask the modem for the signal intensity, so I made a loop that constantly asks for the strenght of the signal, and then proceeds if the signal is strong enough.
If there is signal available, it works just fine. The problem is when there is no signal. I've tried increasing the sleep time, but it seems not to work.
Signal = False
while Signal is not True:
x = ser.readline()
y = x.rstrip()
z = y.decode('utf-8')
ser.write(str.encode('AT+CSQ?\r'))
time.sleep(9)
if (z.startswith("+CSQ")):
a = int(z[5])
if a >= 3:
Signal = True
time.sleep(4)
enough_signal_write()
else:
checking_signal
With long loops, like 10 tries to catch signal, it seems to work strangely, like ignoring if the signal is strong enough sometimes, then finally catching up and running. (I can check this through the strange order of the modem outputs with 'print(z)' line)
I'm feeling like I need something to wait for the modem to respond, and then take actions with this, but I'm not sure how I should do it.
Thanks for you time

Related

Python: How to make a non-blocking while-loop?

How do I make a non-blocking while-loop? I am making a program to control RGB led-strips with my phone using mqtt package. An mqtt command should make Led_Desk[1] False but the while-loop blocks everything else including mqtt-subscription.
I've tried threading, async-await, but nothing's worked so far.
while Led_Desk[1]:
for x in range(1,255):
GREEN.ChangeDutyCycle(round(x/2.55,1)*brightness)
time.sleep(speed)
for x in range(1,255):
RED.ChangeDutyCycle(round(100-x/2.55,1)*brightness)
time.sleep(speed)
for x in range(1,255):
BLUE.ChangeDutyCycle(round(x/2.55,1)*brightness)
time.sleep(speed)
for x in range(1,255):
GREEN.ChangeDutyCycle(round(100-x/2.55,1)*brightness)
time.sleep(speed)
for x in range(1,255):
RED.ChangeDutyCycle(round(x/2.55,1)*brightness)
time.sleep(speed)
for x in range(1,255):
BLUE.ChangeDutyCycle(round(100-x/2.55,1)*brightness)
time.sleep(speed)
One option is to use threads, where each thread handles one LED. Not an unreasonable solution, but probably overkill for what you are trying to accomplish. I think this can be done in another way, using 1 thread.
import time
Leds = [[GREEN, time.clock_gettime_ns(0)], [RED, time.clock_gettime_ns(0)], [BLUE, time.clock_gettime_ns(0)]]
while Led_Desk[1]:
now = time.clock_gettime_ns(0)
for led in Leds:
if led[1] + speed > now:
led[1] = now
led[0].ChangeDutyCycle(round(x/2.55,1)*brightness)
The idea here is to keep track of how long each LED has been in it's current state. Then continuously check those values, and when they exceed the threshold, toggle them.
You will probably need to tweak both the logic in the if statement and the way the times are being assigned to create the flashing pattern you want. But hopefully this is clear enough to serve as a starting point.
Also, the reason (you might have figured this out already) that the original code is preventing the mqtt subscription is because calling sleep in those nested for loops in a single threaded program is going to end up sleeping many, many times, sequentially.
I solved it by wrapping the while loop in a thread and defining 'thread1.daemon = True'. I'm not sure what it does exactly but it allows threads and main loop to run parrallel without blocking.

Duration calculation and control in ROS - furthermore why does this script keep running even after pressing Ctrl-C?

I have written some code to make the turtlebot turn around. The code is working. What I want to know is how fast the turtlebot is running and how I can control it. Forexample, how can I ensure that the turtlebot turns 5 degrees in one minute?
Last part of the question. After pressing Ctrl-C, the turtlebot stops but the script keeps running. Why? and how can I stop that?
this post does not really help.
went through this post .Does that mean that the while loop below runs 5 times a second regardless of the values I put in the for loops? Or does it mean ROS tries its best to make sure that the loop runs 5 times a second to the best of my machine's ability?
Thank you very much.
# 5 HZ
angle = 5
r = rospy.Rate(5);
while not rospy.is_shutdown():
# code to turn
for x in range(0,100):
rospy.loginfo("turn")
turn_cmd.angular.z = radians(angle)
new_angle = (angle + new_angle) % 360
self.cmd_vel.publish(turn_cmd)
r.sleep()
# code to pause
for x in range(0,100):
rospy.loginfo("stop")
turn_cmd.angular.z = radians(0)
self.cmd_vel.publish(turn_cmd)
r.sleep()
def shutdown(self):
# stop turtlebot
rospy.loginfo("Stop turning")
self.cmd_vel.publish(Twist())
rospy.sleep(1)
According to ROS Wiki, the rospy.Rate convenience class makes a best effort to maintain the loop running at the specified frequency by considering the execution time of the loop since the last successful r.sleep(). This means in your case: as long as the code execution time within the loop does not exceed 1/5 seconds, rospy.Rate will make sure the loop runs at 5Hz.
Regarding the script not stopping when pressing Ctrl-C:
KeyboardInterrupt will be handled differently than in normal Python scripts when using rospy.
rospy catches the KeyboardInterrupt signal to set the rospy.is_shutdown() flag to true. This flag is only checked at the end of each loop, therefore if pressing Ctrl-C during the for-loop executions, the script cannot be stopped because the flag is not checked immediately.
A manual way to signal a shutdown of the node is to use rospy.signal_shutdown(). For this, the disable_signals option needs to be set to true when initializing the ROS node (see Section 2.3 here). Note that you will additionally have to manually invoke the correct shutdown routines to ensure a proper cleanup.

Need to end a loop of a subprocess through userinput (GPIO.input)

I need to execute a bash-command in a python program on a raspberry pi, which starts a record, which really starts recording if their is the sound above a certain frequency and finishes after 8sec or if there is silence. After a record finished, it starts itself an waits for new sound to record. Each record is labeled with time. That's the code doing I quoted here:
while GPIO.input(26) == False:
timestamp = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
process = subprocess.Popen ("rec -c 2 -r 192000 --buffer 50000 -b 32" + filepath + timestamp + ".wav sinc 0.1k silence 1 0.1 0.8% 1 0.4 0.8% vol 6 trim 0 8", shell = True)
process.communicate()
As you can see, to finish the loop, the program waits for a GPIO-input signal (push button). I have an extra code which looks for the name of the subprocess and kills it.
But here is my problem: While the loop runs, it only "looks" for input in the millisecond between one record finishes and a new starts". If I push my button during a record, the loop continues after the record. It only breaks if I push in between.
At first, I thought maybe the while-loop is a bad choice...but if I am not wrong, the problem seems to be the running subprocess.
So here is my question: How can I accomplish this record loop but can stop/kill during a running record through user-input by GPIO. (ctrl+c won't be a viable option)
Many thanks
The issue is that the while loop only checks the condition once each time through the loop. The process.communicate() call waits for the rec command to finish, so you are absolutely right - the button is only checked once, just after the rec and before the timestamp = ....
A solution is to watch for the button in a separate thread, running in parallel with the rec. This answer suggests using event detection from the RPi.GPIO library. Try something like this:
should_exit = False
def my_callback(channel):
if GPIO.input(26):
should_exit = True
GPIO.add_event_detect(26, GPIO.RISING, callback=my_callback, bouncetime=300)
while not should_exit:
timestamp = ...
The my_callback routine should run when the button is pressed, and set the should_exit flag. Once the rec finishes, the while will check should_exit and terminate. (Caution - not tested!)

threading in pygame

I've written a program in python and pygame on Linux. My issue is that when I call my function that dials out on a modem and then delivers a message the program pauses until the function has finished. I need a way to subprocess or thread the function. I'm new to python and pygame so this may be simple but everything I've tried has failed.
Also, I'm sure there's probably a more elegant way to process the sounds and pauses. The first sleep gives the modem time to call. The next two are for natural sounding pauses between words and sentences, and the last is to give the entire script time to deliver the message before the modem hangs up.
I'm calling the function and passing variables like this:
A = 'Electronic_Chime.mp3'
B = 'please_check.mp3'
C = 'three.mp3'
contact_user(A,B,C)
And this is the function:
def contact_user( A, B, C ):
ser.write("ATDT441\r") # Attention - Dial via tone number
time.sleep(6)
pygame.mixer.music.load(A)
pygame.mixer.music.play()
time.sleep(2)
pygame.mixer.music.load(B)
pygame.mixer.music.play(1)
time.sleep(2)
pygame.mixer.music.load(C)
pygame.mixer.music.queue(C)
pygame.mixer.music.play()
time.sleep(10)
Any ideas or suggestions would be appreciated.
PS. I have tried:
thread1 = threading.Thread(contact_user(A,B,C))
thread1.start()
The program seems to behave exactly the same even though I have threaded contact_user(A,B,C).
My main issue was that I wasn't passing my arguments to my thread properly. Following is the code that worked properly for me.
My function:
def contact_user(A,B,C):
ser.write("ATDT411\r") # Attention - Dial Tone 411
time.sleep(5) # Wait for modem to finish dialing
pygame.mixer.music.load(A)
pygame.mixer.music.play() # Play attention chime
time.sleep(2) # Wait long enough for first message chime to play before moving on
pygame.mixer.music.load(B)
pygame.mixer.music.play(1) # Play next message
time.sleep(2) # Wait long enough for second message to play before moving on
pygame.mixer.music.load(C) # Load last message
pygame.mixer.music.queue(C) # Queue second instance of last message
pygame.mixer.music.play() # Play last message twice
time.sleep(10) # Wait long enough for last message to play
ser.write("ATH\r") # Attention - Hang up line
ser.write("ATZ\r") # Attention - Reset modem
ser.close()
Calling function from main program:
t = threading.Thread(name = 'dial', target = contact_user, args = (A,B,C))
t.start()
Thank you to everyone that helped me with this. Very appreciated.

Using PySerial is it possible to wait for data?

I've got a Python program which is reading data from a serial port via the PySerial module. The two conditions I need to keep in mind are: I don't know how much data will arrive, and I don't know when to expect data.
Based on this I have came up with the follow code snippets:
#Code from main loop, spawning thread and waiting for data
s = serial.Serial(5, timeout=5) # Open COM5, 5 second timeout
s.baudrate = 19200
#Code from thread reading serial data
while 1:
tdata = s.read(500) # Read 500 characters or 5 seconds
if(tdata.__len__() > 0): #If we got data
if(self.flag_got_data is 0): #If it's the first data we recieved, store it
self.data = tdata
else: #if it's not the first, append the data
self.data += tdata
self.flag_got_data = 1
So this code will loop forever getting data off the serial port. We'll get up to 500 characters store the data, then alert the main loop by setting a flag. If no data is present we'll just go back to sleep and wait.
The code is working, but I don't like the 5s timeout. I need it because I don't know how much data to expect, but I don't like that it's waking up every 5 seconds even when no data is present.
Is there any way to check when data becomes available before doing the read? I'm thinking something like the select command in Linux.
Note: I found the inWaiting() method, but really that seems it just change my "sleep" to a poll, so that's not what I want here. I just want to sleep until data comes in, then go get it.
Ok, I actually got something together that I like for this. Using a combination of read() with no timeout and the inWaiting() method:
#Modified code from main loop:
s = serial.Serial(5)
#Modified code from thread reading the serial port
while 1:
tdata = s.read() # Wait forever for anything
time.sleep(1) # Sleep (or inWaiting() doesn't give the correct value)
data_left = s.inWaiting() # Get the number of characters ready to be read
tdata += s.read(data_left) # Do the read and combine it with the first character
... #Rest of the code
This seems to give the results I wanted, I guess this type of functionality doesn't exist as a single method in Python
You can set timeout = None, then the read call will block until the requested number of bytes are there. If you want to wait until data arrives, just do a read(1) with timeout None. If you want to check data without blocking, do a read(1) with timeout zero, and check if it returns any data.
(see documentation https://pyserial.readthedocs.io/en/latest/)
def cmd(cmd,serial):
out='';prev='101001011'
serial.flushInput();serial.flushOutput()
serial.write(cmd+'\r');
while True:
out+= str(serial.read(1))
if prev == out: return out
prev=out
return out
call it like this:
cmd('ATZ',serial.Serial('/dev/ttyUSB0', timeout=1, baudrate=115000))

Categories

Resources