Stepper motor on raspberry pi callibration issues in python script - python

I am writing python a script in which I run a stepper motor over the GPIO pins of a raspberry pi and a leadshine driver. The script works by itself, and I have callibrated the stepper by making it run 50 times back and forth at different speeds using the stepper_run function by itself. And this works perfectly. My goal is to run a scanner timed with the stepper motor, using a subprocess call to a scanimage command.
The problem is that steps start to go slightly out of sync (say 100 steps on 12000) when I do other things in the script simultaneously, or right before/after I run the stepper. It is as if the python script isn't able to count its own pulses properly because it is busy doing something else?
Stepper motor definition:
def stepper_run(steps, speed, direction, delay):
'''
wrapper to run stepper
'''
steps = int(steps * (resolution/200))
speed = (speed / (resolution/200))
GPIO.output(ENA, GPIO.HIGH)
print('ENA set to LOW - Controller Enabled')
time.sleep(delay) # pause due to a possible change direction
set_direction(direction)
print('DIR set to LOW - Moving at ' + str(speed))
print('Controller PUL being driven.')
for x in range(steps):
GPIO.output(PUL, GPIO.HIGH)
time.sleep(speed)
GPIO.output(PUL, GPIO.LOW)
time.sleep(speed)
GPIO.output(ENA, GPIO.LOW)
print('ENA set to LOW - Controller Disabled')
#time.sleep(.5) # pause for possible change direction
return
Calling the stepper:
def canoscanlide400(currentposition):
'''
EDIT THIS DEFINITION TO MAKE THE SCANNER SEQUENCE
'''
#STEP1
stepper_run(12190, 0.00059, True, 0.32)
#STEP2
stepper_run(11740, 0.000462, False, 0.32)
#RETURN TO 0
stepper_run(450, 0.000485, False, 0.32)
return currentposition
The function that calls the scanner:
def scan(filename,device,fileformat,dpi,xsize,ysize):
'''
creates two files to hold the image and errors, respectively
takes 6 arguments: filename, device, fileformat, resolution in dpi, x size and y size for the scanner
'''
with open(filename,'w+') as write_handle:
with open('err.txt','a+') as ferr:
out=subprocess.Popen(['scanimage', '-d', device, '--resolution='+dpi, '-x '+xsize, '-y '+ysize, '--format='+fileformat, '--mode=Color'],stdout=write_handle,stderr=ferr)
And finally this is how I call everything:
if (device and 'no scanners' not in device and 'some other error' not in device):
print (device)
time.sleep(5)
for i in range (10):
scan(filename,device,fileformat,dpi,xsize,ysize)
currentposition = canoscanlide400(currentposition)
print (currentposition)
time.sleep(5)

Related

Variables in callback function wont update even when the loop reiterates

The task I am currently working on is:
A robot that is installed with a LIDAR sensor and placed in an enclosed warehouse. The robot initially is placed at any random position in the environment. My task is to guide the robot around the warehouse using the values from the sensor, such as wall following etc.
The problem I am facing currently is that I need the robot to start at a position before it does the wall following task. This position is such that the robot first needs to get close to the nearest wall and place itself parallel to the wall, ready to begin the wall following.
I created two '.py' files, a server file and a client file.
The client file calls the server file to do the wall search task. Once the task is complete the rest of the code in the client file is to begin the wall following. I included the code with the problems in it.
msg.ranges[360] (value of front laser range) is the variable that I need help with.
# The lidar sensor has a range form 0 to 180 degress with index range 0-720. The laser at 360 is the front laser or the laser with 0 angle.
# The laser at 0 and 719 are at -90 and 90 angle.
#! /usr/bin/env python
import rospy
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
from rosject.srv import findwall, findwallResponse #custom service message
def callback(msg):
r = list(msg.ranges)
minimum_value = min(r)
print(minimum_value)
index_minimum = r.index(minimum_value)
ray_angle = msg.angle_min + (index_minimum * msg.angle_increment)
#The distance and angle of an obstacle with respect to the laser scanner device must be calculated using
#The fields angle_min and angle_increment: ray_angle
print(ray_angle)
#Logic for duration of rotation
countdown_sec1 = 0
if ray_angle < 0:
twist_instance.angular.z = 0.2 #angular velocity in radians/second
else:
twist_instance.angular.z = -0.2
rotation_duration = (abs(ray_angle)/0.2) - 1
while countdown_sec1 <= rotation_duration:
pub.publish(twist_instance)
rate.sleep()
countdown_sec1 = countdown_sec1 + 1
#Once the robot faces the wall it is stopped
twist_instance.angular.z = 0
twist_instance.linear.x = 0
pub.publish(twist_instance)
rate.sleep()
print('front distance',msg.ranges[360]) #once stopped, I print the range of the front laser facing the wall
#This is where I faced the problem. the value that prints for the front laser is incorrect. I am unable to understand why.
#The following code is just to move the robot forward until it's within a certain distance from the wall.
difference = 0.3
forward_duration = round(difference/0.1)
countdown_sec2 = 0
twist_instance.linear.x = 0.1
twist_instance.angular.z = 0
while countdown_sec2 <= forward_duration:
pub.publish(twist_instance)
rate.sleep()
countdown_sec2 = countdown_sec2 + 1
twist_instance.linear.x = 0
pub.publish(twist_instance)
print('final distance', msg.ranges[360]) #Even here once the task is complete the the final value remains the same and incorrect.
# I let the callback run again and even then the value for the front laser does not change
def my_callback(request):
print('i am working boy')
my_response = findwallResponse()
my_response.wallfound = True
return my_response
if __name__ == '__main__':
rospy.init_node('findwall_node')
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)
sub = rospy.Subscriber('/scan', LaserScan, callback)
twist_instance = Twist()
rate = rospy.Rate(1)
my_service = rospy.Service('/find_wall', findwall, my_callback)
rospy.spin()
Where am I going wrong, why wont the value for msg.ranges[360] not change when the loop is run again by rospy.spin()?
msg won't change as you're still inside callback(msg). In your code, callback(msg) will be called everytime sub will receive a LiDAR scan message.
I suggest that you separate the code that receives the message from the code that waits and publishes, as you will lose laser scan messages if your node is still processing a msg.
Here is a way to do it in ROS1:
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int64, String
class MyNode:
# CLASS_VARIABLES
def __init__(self, start_value=0):
# INSTANCE_VARIABLES
self.counter = start_value
pass
def counter_callback(self, msg):
counter_data = msg.data
self.counter += counter_data
if __name__ == "__main__":
rospy.init_node("my_node")
rate = rospy.Rate(2)
node = MyNode()
sub = rospy.Subscriber("mynode_pub_topic", Int64, node.counter_callback)
string_pub = rospy.Publisher("mynode_pub_topic", String, queue_size=10)
string_instance = String()
while not rospy.is_shutdown():
string_instance.data = str(node.counter)
string_pub.publish(string_instance)
rate.sleep()
Other improvements could be to use publisher and subscribers as class attributes, as it is now done in ROS2.

I can't get my Raspbery pi camera to change effects with if statements

I'm working on a project for my computer science class. I've attached a camera and a distance sensor to my raspberry pi. the basic idea of the program is that that the pi will get a reading from the motion sensor and then depending on the reading it will take a picture with the camera and display a different camera effect and then keep updating the distance and the pictures on a loop.
My problem is that it will get stuck on the first camera effect that is activated, what I mean by this is if the distance sensor doesn't detect anything it will take a picture with no effect, if it picks up a closer reading it will display a negative, but if it doesn't detect anything initially it will continue to take pictures with no effect no matter what the reading actually is.
It also works the other way around as in if it detects something close it will continue to display a negative picture. I'm assuming what I did wrong has something to do with how I set up my loops.
from gpiozero import DistanceSensor
from gpiozero import DistanceSensor
from time import sleep
ultrasonic = DistanceSensor(echo=17, trigger=4)
ultra = ultrasonic.distance
camera = PiCamera()
for x in range(5):
print(ultrasonic.distance)
sleep(2)
while ultra == 1.0:
camera.start_preview(alpha=192)
sleep(5)
camera.capture("/home/pi/final.jpg")
camera.stop_preview()
False
while ultra <= 0.05:
camera.start_preview(alpha=192)
camera.image_effect = 'negative'
sleep(5)
camera.capture("/home/pi/negativedistancefinal.jpg")
camera.stop_preview()
False
while ultra >= 0.05:
camera.start_preview(alpha=192)
camera.image_effect = 'colorswap'
sleep(5)
camera.capture("/home/pi/colorswapdistancefinal.jpg")
camera.stop_preview()
Try this:
from gpiozero import DistanceSensor
from gpiozero import DistanceSensor
from time import sleep
ultrasonic = DistanceSensor(echo=17, trigger=4)
camera = PiCamera()
for x in range(5):
ultra = ultrasonic.distance
print(ultrasonic.distance)
if ultra == 1.0:
camera.start_preview(alpha=192)
sleep(5)
camera.capture("/home/pi/final.jpg")
camera.stop_preview()
elif ultra <= 0.05:
camera.start_preview(alpha=192)
camera.image_effect = 'negative'
sleep(5)
camera.capture("/home/pi/negativedistancefinal.jpg")
camera.stop_preview()
elif ultra >= 0.05:
camera.start_preview(alpha=192)
camera.image_effect = 'colorswap'
sleep(5)
camera.capture("/home/pi/colorswapdistancefinal.jpg")
camera.stop_preview()
sleep(2)
You need to update your readings and put if instead of while

pygame program on boot being killed with no errors

I have a program that is being executed on reboot via crontab. The script and programs work as expected if I run the script from terminal manually myself, but on running the script on boot its as if my RoverDriver.py program is being killed for no reason with no errors.
#reboot sleep 15 && sh /home/pi/Desktop/Rover-Driver/launcher.sh >/home/pi/Desktop/Rover-Driver/logs/cronlog 2>&1
The shell script is:
cd /
cd /home/pi/Desktop/Rover-Driver
sudo python3 bluetooth.py
cd /
The bluetooth.py program checks to see if our bluetooth wireless controller is connected to the pi, if so start the driver program
import pexpect
import time
attempts = 0
while attempts < 30:
child = pexpect.spawn('bluetoothctl')
child.sendline('paired-devices')
output = child.read_nonblocking(size = 200, timeout = -1).decode('utf-8')
target = 'Xbox Wireless Controller'
if output.find(target > -1:
print("Target Found")
print(pexpect.run('python RoverDriver.py'))
break
else:
print("No target found")
attempts = attempts + 1
time.sleep(1)
The RoverDiver.py is:
import pyfirmata
import pygame
import time
from pygame.locals import *
pygame.init()
# controller setup
joysticks = []
for i in range(pygame.joystick.get_count()):
joysticks.append(pygame.joystick.Joystick(i))
joysticks[-1].init()
controller = joysticks[0]
board = pyfirmata.ArduinoMega('/dev/ttyACM0')
# setting pins
onboard_led = board.get_pin('d:13:o')
l_motor_pin = board.get_pin('d:11:p')
r_motor_pin = board.get_pin('d:12:p')
motor_toggle_pin = board.get_pin('d:24:o')
# constantly updates statuses for example if reading analog input from potentiometer
it = pyfirmata.util.Iterator(board)
it.start()
motor_toggle_pin.write(0)
l_motor_pin.write(.49804)
r_motor_pin.write(.49804)
print("Have not entered while loop yet")
while(1):
pygame.event.pump()
# puts rover in "stop" state if no input, avoids rover from "running away"
motor_toggle_pin.write(0)
l_motor_pin.write(.49804)
r_motor_pin.write(.49804)
if(controller.get_button(11)):
print("Exiting...")
exit()
# divider ensure only half power given unless "A" button pressed down
divider = 0.125
reverse = False
right_power = controller.get_axis(4)
left_power = controller.get_axis(5)
# "A" button pressed, give full power
if(controller.get_button(0)):
divider = 0.25
right_power = divider * (right_power + 1)
left_power = divider * (left_power + 1)
# Bumpers are pressed, go reverse. Must be doing both to avoid breaking bot
if(controller.get_button(6) and controller.get_button(7)):
left_power = 0.5 - left_power
right_power = 0.5 - right_power
reverse = True
else:
left_power = 0.5 + left_power
right_power = 0.5 + right_power
# send motors their values
motor_toggle_pin.write(1)
l_motor_pin.write(left_power)
r_motor_pin.write(right_power)
print(f"L Power:{left_power} |R Power:{right_power}")
# avoid cpu overloading
time.sleep(.05)
# after exiting while loop, "turn off" motors
motor_toggle_pin.write(0)
l_motor_pin.write(.49804)
r_motor_pin.write(.49804)
print("End.")
I am expecting a rather large blob of text in the logs (because of the print statement of power levels), or at least the before/after print statements of the while loop however all i get is this:
Target Found.
b'pygame 1.9.4post1\r\nHello from the pygame community. https://www.pygame.org/contribute.html\r\n'
I know that its formatted that way because I am not decoding it. I am hypothesizing it has something to do with pygame but not sure what?
EDIT: To anyone who comes across this post. I switched my controls completely and no longer even rely on pygame. I switched to connecting to the pi via sockets and sending command strings over. This still needed to be setup on boot so I tried using crontab (unsuccessfully again) and instead found out about systemd which works beautifully.

Python, RPi and GPIO - control engine

On my Raspberry Pi I have code written in Python that control engine on my vehicle. I control engine by GPIO. It works but the problem is that when I set io to go and then I set io to change direction it stops. Why it cannot do two things in one time?
This is my code:
import RPi.GPIO as io
import time
import serial
class TankManager:
pLeft = 0
pRight = 0
turnBarrel = 0
liftBarrel = 0
def __init__(self):
io.setmode(io.BCM)
def goahead(self, speed):
if(speed > 25) : speed = 25
io.setup(12, io.OUT)
TankManager.pLeft = io.PWM(12, 2.2)
TankManager.pLeft.start(1)
io.setup(13, io.OUT)
TankManager.pRight = io.PWM(13, 2.2)
TankManager.pRight.start(1)
io.setup(20, io.OUT)
io.output(20, False)
io.setup(21, io.OUT)
io.output(21, False)
return
def gostop(self):
if 'pLeft' in globals():
TankManager.pLeft.stop()
if 'pRight' in globals():
TankManager.pRight.stop()
io.cleanup();
return
def turnright(self):
io.setup(12, io.OUT)
TankManager.pLeft = io.PWM(12, 2.2)
TankManager.pLeft.start(1)
io.setup(21, io.OUT)
io.output(21, False)
return
def turnbarrelstop(self):
if 'turnBarrel' in globals():
TankManager.turnBarrel.stop()
io.cleanup();
return
And for example when I make turnbarrelstop the tank stop barrel but it stop going too. Maybe the reason is that I call io.cleanup() ? And it stop all GPIO signals?
I want to stop barrel but without stop going tank.
Do not call io.cleanup(). I would recommend do not call it in gostop() function too. And use it only if you are exiting the program.
You just need to change (level or PWM whatever applicable) of the specific io PIN.

Raspberry Pi 3 + temperature sensor + servo motor

I have raspberry pi 3 and trying to create smart green house model. This model should open window if temperature is too hight.
I am new writing codes in Python, found several examples: 1. for temperature sensor and 2. for servo motor to rotating.
Could anyone help me with servo motor? I would like to move servo for example to 30° if temperature is 20°C, if it is 21°C move 40° servo and so on.
I have Python code:
import sys
import Adafruit_DHT
import time
import wiringpi
sensor_args = { '11': Adafruit_DHT.DHT11,
'22': Adafruit_DHT.DHT22,
'2302': Adafruit_DHT.AM2302 }
if len(sys.argv) == 3 and sys.argv[1] in sensor_args:
sensor = sensor_args[sys.argv[1]]
pin = sys.argv[2]
else:
print('usage: sudo ./Adafruit_DHT.py [11|22|2302] GPIOpin#')
print('example: sudo ./Adafruit_DHT.py 2302 4 - Read from an AM2302
connected to GPIO #4')
sys.exit(1)
humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
if humidity is not None and temperature is not None:
print('Temp={0:0.1f}* Humidity={1:0.1f}%'.format(temperature, humidity))
else:
print('Failed to get reading. Try again!')
sys.exit(1)
temp=temperature
text_file = open("output.txt", "w")
text_file.write("%s" %(temp))
text_file.close()
wiringpi.wiringPiSetupGpio()
wiringpi.pinMode(18,wiringpi.GPIO.PWM_OUTPUT)
wiringpi.pwmSetMode(wiringpi.GPIO.PWM_MODE_MS)
wiringpi.pwmSetClock(192)
wiringpi.pwmSetRange(2000)
delay_period = 0.01
while temp==20.0:
for pulse in range(50,250,1):
wiringpi.pwmWrite(18,50)
time.sleep(delay_period)
for pulse in range(250,50,-1):
wiringpi.pwmWrite(18,pulse)
time.sleep(delay_period)
Part about servo motor ir example I found on the internet. I need to replace "while" to "if". I tried by myself, but rotor all the time spin to the same angle.
Can anyone help me with this little part of code?
Second question, how can I run this command in terminal "sudo python servo.py 11 17" on raspberry pi automatically in every 10 mminutes and when raspberry pi is turned on?
Thanks for your help!
The pulse is how you are controlling the position of the servo in your code:
wiringpi.pwmWrite(18,pulse)
See this example:
https://learn.adafruit.com/adafruits-raspberry-pi-lesson-8-using-a-servo-motor?view=all
Depending on your servo, a pulse value of 100 move the servo all the way left (closed in this example), and 200 all the way to the right (open in this example). You need to find these values by reading the datasheet or by experimentation. Once you have these, here is how you would set the position of the servo:
min_servo_val = 100
max_servo_val = 200
wiringpi.pwmWrite(18, min_servo_val) # Move all the way left
time.sleep(1) # Wait a second
wiringpi.pwmWrite(18, max_servo_val) # Move all the way right
Now you can either write a function that translates temperature to a servo position between min_servo_val and max_servo_val, or use a simple if statement. Here is an example of a function to translate temp into a pulse (servo position):
def get_servo_position(temp):
min_servo_val = 100 # Pulse value at which window is all the way closed closed
max_servo_val = 200 # Pulse value at which window is all the way open
full_closed_temp = 20.0 # Temperature at which window is completely closed
full_open_temp = 26.0 # Temperature at which window is completely open
if temp <= full_closed_temp:
return min_servo_val
elif temp >= full_open_temp:
return max_servo_val
else:
return ((temp - full_closed_temp) / (full_open_temp - full_closed_temp)) * (max_servo_val - min_servo_val) + min_servo_val
Now you can do:
print get_servo_position(19) # 100 all the way closed
print get_servo_position(22) # 133.3, or 33% of the way between min_servo_val and max_servo_val
print get_servo_position(25) # 183.3, or 83% of the way between min_servo_val and max_servo_val
print get_servo_position(27) # 200 all the way open
Now you need a loop that checks the temperature every ten minutes and adjusts the servo position. Something like this:
while True:
humidity, temp = Adafruit_DHT.read_retry(sensor, pin) # Get temperature.
position = get_servo_position(temp) # Get the servo position
wiringpi.pwmWrite(18,position) # Move to the position
time.sleep(10*60) # Wait 10 minutes
Putting it all together, your script should look something like this:
import sys
import Adafruit_DHT
import time
import wiringpi
dht_pin = 17 # GPIO conencted to DHT
servo_pin = 18 # GPIO connected to servo
dht_sensor = Adafruit_DHT.DHT11 # Put your sensor here, or set it from command line args
min_servo_val = 100 # Pulse value at which window is all the way closed closed
max_servo_val = 200 # Pulse value at which window is all the way open
full_closed_temp = 20.0 # Temperature at which window is completely closed
full_open_temp = 26.0 # Temperature at which window is completely open
def get_servo_position(temp):
if temp <= full_closed_temp:
return min_servo_val
elif temp >= full_open_temp:
return max_servo_val
else:
return ((temp - full_closed_temp) / (full_open_temp - full_closed_temp)) * (max_servo_val - min_servo_val) + min_servo_val
def main_loop():
while True:
humidity, temp = Adafruit_DHT.read_retry(dht_sensor, dht_pin) # Get temperature.
position = get_servo_position(temp) # Get the servo position
wiringpi.pwmWrite(18,position) # Move to the position
time.sleep(10*60) # Wait 10 minutes
if __name__ == '__main__':
# If you need to get command line arguments, do it below, otherwise just set the pins and other settings at the top of this script.
# For example...
# dht_pin = sys.argv[1]
# servo_pin = sys.argv[2]
# Set up servo
wiringpi.wiringPiSetupGpio()
wiringpi.pinMode(18,wiringpi.GPIO.PWM_OUTPUT)
wiringpi.pwmSetMode(wiringpi.GPIO.PWM_MODE_MS)
wiringpi.pwmSetClock(192)
wiringpi.pwmSetRange(2000)
# Enter main loop
main_loop()
Note that I left out command line arg parsing. If you need those you can add them right after if __name__ == '__main__':
Finally, making a script run on startup is a well covered topic:
Start shell script on Raspberry Pi startup

Categories

Resources