Adding an additional Servo to Picar-x

Hi, I want to use an additional servo for a project I am working on. How do I read input and code it?

If you want to add extra servos to picar-x, you can refer to our picarx.py code to expand on the original code, which requires you to modify the code yourself.
You need to modify the code by yourself. So you need to have some basic programming skills to expand the code.

Thank you, that works

1 Like

I modified the code on picar-x.py to add the additional servo, see below. When I try to run it, it gives me the following message: Attribute Error: ‘Picarx’ object has no attribute ‘set_nerf_gun_angle’.

The code I use to modify the angle of the additional servo is px.set_nerf_gun_angle(30)

The modified picar-x.py code below:

from robot_hat import Pin, ADC, PWM, Servo, fileDB
from robot_hat import Grayscale_Module, Ultrasonic
from robot_hat import Pin, ADC, PWM, Servo, fileDB
from robot_hat import Grayscale_Module, Ultrasonic, reset_mcu

Remove this line: from robot_hat.utils import reset_mcu, run_comman2

import time
import os
import time
import os

reset robot_hat

reset_mcu()
time.sleep(0.2)

def constrain(x, min_val, max_val):
‘’’
Constrains value to be within a range.
‘’’
return max(min_val, min(max_val, x))

class Picarx(object):
CONFIG = ‘/opt/picar-x/picar-x.conf’

DEFAULT_LINE_REF = [1000, 1000, 1000]
DEFAULT_CLIFF_REF = [500, 500, 500]

DIR_MIN = -35
DIR_MAX = 35
CAM_PAN_MIN = -90
CAM_PAN_MAX = 90
CAM_TILT_MIN = -35
CAM_TILT_MAX = 65

NERF_GUN_MIN = -90
NERF_GUN_MAX = 90


PERIOD = 4095
PRESCALER = 10
TIMEOUT = 0.02

# servo_pins: camera_pan_servo, camera_tilt_servo, direction_servo
# motor_pins: left_swicth, right_swicth, left_pwm, right_pwm
# grayscale_pins: 3 adc channels
# ultrasonic_pins: tring, echo
# config: path of config file
def __init__(self, 
            servo_pins:list=['P0', 'P1', 'P2', 'P3'],
            motor_pins:list=['D4', 'D5', 'P12', 'P13'],
            grayscale_pins:list=['A0', 'A1', 'A2'],
            ultrasonic_pins:list=['D2','D3'],
            config:str=CONFIG,
            ):

    # --------- config_flie ---------
    self.config_flie = fileDB(config, 774, os.getlogin())

    # --------- servos init ---------
    self.cam_pan = Servo(servo_pins[0])
    self.cam_tilt = Servo(servo_pins[1])   
    self.dir_servo_pin = Servo(servo_pins[2])
    self.nerf_gun = Servo(servo_pins[3])
    
    
    # get calibration values
    self.dir_cali_val = float(self.config_flie.get("picarx_dir_servo", default_value=0))
    self.cam_pan_cali_val = float(self.config_flie.get("picarx_cam_pan_servo", default_value=0))
    self.cam_tilt_cali_val = float(self.config_flie.get("picarx_cam_tilt_servo", default_value=0))
    self.nerf_gun_cali_val = float(self.config_flie.get("picarx_nerf_gun_servo", default_value=0))
    
    # set servos to init angle
    self.dir_servo_pin.angle(self.dir_cali_val)
    self.cam_pan.angle(self.cam_pan_cali_val)
    self.cam_tilt.angle(self.cam_tilt_cali_val)
    self.nerf_gun.angle(self.nerf_gun_cali_val)
    
    # --------- motors init ---------
    self.left_rear_dir_pin = Pin(motor_pins[0])
    self.right_rear_dir_pin = Pin(motor_pins[1])
    self.left_rear_pwm_pin = PWM(motor_pins[2])
    self.right_rear_pwm_pin = PWM(motor_pins[3])
    self.motor_direction_pins = [self.left_rear_dir_pin, self.right_rear_dir_pin]
    self.motor_speed_pins = [self.left_rear_pwm_pin, self.right_rear_pwm_pin]
    # get calibration values
    self.cali_dir_value = self.config_flie.get("picarx_dir_motor", default_value="[1, 1]")
    self.cali_dir_value = [int(i.strip()) for i in self.cali_dir_value.strip().strip("[]").split(",")]
    self.cali_speed_value = [0, 0]
    self.dir_current_angle = 0
    # init pwm
    for pin in self.motor_speed_pins:
        pin.period(self.PERIOD)
        pin.prescaler(self.PRESCALER)

    # --------- grayscale module init ---------
    adc0, adc1, adc2 = [ADC(pin) for pin in grayscale_pins]
    self.grayscale = Grayscale_Module(adc0, adc1, adc2, reference=None)
    # get reference
    self.line_reference = self.config_flie.get("line_reference", default_value=str(self.DEFAULT_LINE_REF))
    self.line_reference = [float(i) for i in self.line_reference.strip().strip('[]').split(',')]
    self.cliff_reference = self.config_flie.get("cliff_reference", default_value=str(self.DEFAULT_CLIFF_REF))
    self.cliff_reference = [float(i) for i in self.cliff_reference.strip().strip('[]').split(',')]
    # transfer reference
    self.grayscale.reference(self.line_reference)

    # --------- ultrasonic init ---------
    tring, echo= ultrasonic_pins
    self.ultrasonic = Ultrasonic(Pin(tring), Pin(echo))
    
def set_motor_speed(self, motor, speed):
    ''' set motor speed
    
    param motor: motor index, 1 means left motor, 2 means right motor
    type motor: int
    param speed: speed
    type speed: int      
    '''
    motor -= 1
    if speed >= 0:
        direction = 1 * self.cali_dir_value[motor]
    elif speed < 0:
        direction = -1 * self.cali_dir_value[motor]
    speed = abs(speed)
    if speed != 0:
        speed = int(speed /2 ) + 50
    speed = speed - self.cali_speed_value[motor]
    if direction < 0:
        self.motor_direction_pins[motor].high()
        self.motor_speed_pins[motor].pulse_width_percent(speed)
    else:
        self.motor_direction_pins[motor].low()
        self.motor_speed_pins[motor].pulse_width_percent(speed)

def motor_speed_calibration(self, value):
    self.cali_speed_value = value
    if value < 0:
        self.cali_speed_value[0] = 0
        self.cali_speed_value[1] = abs(self.cali_speed_value)
    else:
        self.cali_speed_value[0] = abs(self.cali_speed_value)
        self.cali_speed_value[1] = 0

def motor_direction_calibrate(self, motor, value):
    ''' set motor direction calibration value
    
    param motor: motor index, 1 means left motor, 2 means right motor
    type motor: int
    param value: speed
    type value: int
    '''      
    motor -= 1
    if value == 1:
        self.cali_dir_value[motor] = 1
    elif value == -1:
        self.cali_dir_value[motor] = -1
    self.config_flie.set("picarx_dir_motor", self.cali_dir_value)

def dir_servo_calibrate(self, value):
    self.dir_cali_val = value
    self.config_flie.set("picarx_dir_servo", "%s"%value)
    self.dir_servo_pin.angle(value)

def set_dir_servo_angle(self, value):
    self.dir_current_angle = constrain(value, self.DIR_MIN, self.DIR_MAX)
    angle_value  = value + self.dir_cali_val
    self.dir_servo_pin.angle(angle_value)
    
def nerf_gun_servo_calibrate(self, value):
    self.nerf_gun_cali_val = value
    self.config_flie.set("picarx_nerf_gun_servo", "%s"%value)
    self.nerf_gun.angle(value)

def cam_pan_servo_calibrate(self, value):
    self.cam_pan_cali_val = value
    self.config_flie.set("picarx_cam_pan_servo", "%s"%value)
    self.cam_pan.angle(value)

def cam_tilt_servo_calibrate(self, value):
    self.cam_tilt_cali_val = value
    self.config_flie.set("picarx_cam_tilt_servo", "%s"%value)
    self.cam_tilt.angle(value)
    
def set_cam_pan_angle(self, value):
    value = constrain(value, self.CAM_PAN_MIN, self.CAM_PAN_MAX)
    self.cam_pan.angle(-1*(value + -1*self.cam_pan_cali_val))

def set_camera_tilt_angle(self, value):
    value = constrain(value, self.CAM_TILT_MIN, self.CAM_TILT_MAX)
    self.cam_tilt.angle(-1*(value + -1*self.cam_tilt_cali_val))

def set_nerf_gun_angle(self, value):
    self.nerf_gun_servo_calibrate(value)
    value = constrain(value, self.NERF_GUN_MIN, self.NERF_GUN_MAX)
    self.nerf_gun.angle(value)
    #self.nerf_gun.angle(-1*(value ))

def set_power(self, speed):
    self.set_motor_speed(1, speed)
    self.set_motor_speed(2, speed) 

def backward(self, speed):
    current_angle = self.dir_current_angle
    if current_angle != 0:
        abs_current_angle = abs(current_angle)
        if abs_current_angle > 40:
            abs_current_angle = 40
        power_scale = (100 - abs_current_angle) / 100.0 
        if (current_angle / abs_current_angle) > 0:
            self.set_motor_speed(1, -1*speed)
            self.set_motor_speed(2, speed * power_scale)
        else:
            self.set_motor_speed(1, -1*speed * power_scale)
            self.set_motor_speed(2, speed )
    else:
        self.set_motor_speed(1, -1*speed)
        self.set_motor_speed(2, speed)  

def forward(self, speed):
    current_angle = self.dir_current_angle
    if current_angle != 0:
        abs_current_angle = abs(current_angle)
        if abs_current_angle > 40:
            abs_current_angle = 40
        power_scale = (100 - abs_current_angle) / 100.0
        if (current_angle / abs_current_angle) > 0:
            self.set_motor_speed(1, 1*speed * power_scale)
            self.set_motor_speed(2, -speed) 
        else:
            self.set_motor_speed(1, speed)
            self.set_motor_speed(2, -1*speed * power_scale)
    else:
        self.set_motor_speed(1, speed)
        self.set_motor_speed(2, -1*speed)                  

def stop(self):
    self.set_motor_speed(1, 0)
    self.set_motor_speed(2, 0)

def get_distance(self):
    return self.ultrasonic.read()

def set_grayscale_reference(self, value):
    if isinstance(value, list) and len(value) == 3:
        self.line_reference = value
        self.grayscale.reference(self.line_reference)
        self.config_flie.set("line_reference", self.line_reference)
    else:
        raise ValueError("grayscale reference must be a 1*3 list")

def get_grayscale_data(self):
    return list.copy(self.grayscale.read())

def get_line_status(self,gm_val_list):
    return self.grayscale.read_status(gm_val_list)

def set_line_reference(self, value):
    self.set_grayscale_reference(value)

def set_cliff_reference(self, value):
    if isinstance(value, list) and len(value) == 3:
        self.cliff_reference = value
        self.config_flie.set("cliff_reference", self.cliff_reference)
    else:
        raise ValueError("grayscale reference must be a 1*3 list")

if name == “main”:
px = Picarx()
px.forward(50)
time.sleep(1)
px.stop()

After modifying the picarx.py file, do you do a reinstallation, which is required for it to take effect:
cd ~/picar-x
sudo python3 setup.py install
This may be required for bookworm systems:
sudo python3 setup.py install --break-system-packages

I have tried with the reinstallation sudo python3 setup.py install but it still not working
What do you mean by break system packages? Thank you

“sudo python3 setup.py install --break-system-packages”
This is a one-line install command.
If you are currently using a bookworm system, running sudo python3 setup.py install install command needs to be followed by --break-system-packages, so you need to run:
sudo python3 setup.py instal --break-system-packages
Once the installation is successful, you can run the code script again to see if it solves the problem. If you still get an error, please provide us a screenshot of the error.