Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pca9685? #20

Open
srvoets opened this issue Oct 12, 2024 · 6 comments
Open

pca9685? #20

srvoets opened this issue Oct 12, 2024 · 6 comments

Comments

@srvoets
Copy link

srvoets commented Oct 12, 2024

I just got two mg995 servos in my pose'n'stay skeleton, one for jaw, one for head movement left and right.

I am using a pca9685 to control the two servos, I understand it may be overkill but it does make power distribution easy.

Is there anyway to use chatterpi with the pca breakout board?

Edit to add:

Also, regardless of above, since I am only using 2 servos, I would gladly connect to GPIO and power servos separately, however, and correct me if I am mistaken, but ChatterPi supports only 1 servo? I have 2, one for jaw, one for head left/right.

@srvoets
Copy link
Author

srvoets commented Oct 13, 2024

Solved by Issue #1

I know it isnt a TON of work, but Ive spent a lot of time getting everything into the skeleton, if ChatterPi could use a servo controller, and control another servo for head left/right or something it would be a true AIO solution for these types of things.

I am controlling my skeleton servos through viam currently, which I am new to, I wonder if Viam can call chatter pi for its capabilities, but use other modules for when it is 'ambient'

@ViennaMike
Copy link
Owner

I'm not familiar with viam. ChatterPi is currently set up to either autostart or be started by command, but it shouldn't be hard to modify it to be called by another program.

I currently have my skeletons to be motionless until triggered. Then, when triggered, ChatterPi controls the jaw and sends a signal to an external servo controller (a Maestro by Pololu) to control the other servos. I currently run a pre-programmed routine on the Maestro). But I also did a related project where I used WiFi to sent positions to a Pi to control the skull. The other program received the position info and moved the nod, tilt, and turn servos, while the audio fed into a modified version of ChatterPi to control the jaw. So you can definitely do this.

@srvoets
Copy link
Author

srvoets commented Oct 15, 2024

I'm not familiar with viam. ChatterPi is currently set up to either autostart or be started by command, but it shouldn't be hard to modify it to be called by another program.

I currently have my skeletons to be motionless until triggered. Then, when triggered, ChatterPi controls the jaw and sends a signal to an external servo controller (a Maestro by Pololu) to control the other servos. I currently run a pre-programmed routine on the Maestro). But I also did a related project where I used WiFi to sent positions to a Pi to control the skull. The other program received the position info and moved the nod, tilt, and turn servos, while the audio fed into a modified version of ChatterPi to control the jaw. So you can definitely do this.

I would very much like to send the servo commands / positions over wifi, mqtt, home assistant any of those would be awesome, as I do not have any type of external servo controller. Can you share the code you used?

@ViennaMike
Copy link
Owner

I used xmlrpc over wifi to send sensor positions to the pi that controls the skull, and then that pi converted the senor readings to commands that were sent to the Maestro servo controller. But you could certainly send servo commands straight from the receiving Pi. The receiving pi just uses the xmlrpc.client library ( in particular, xmlrpc.client.ServerProxy) to send whatever function calls you've defined on the server end.

The server end imports:

from xmlrpc.server import SimpleXMLRPCServer
import xmlrpc.client

xmlrpc just sends the function calls over WiFi to the server side:

# calibration (sys, gyro, accel, mag)
with xmlrpc.client.ServerProxy("http://pi0wsensor:8000") as proxy:
    status = proxy.get_calibration_status()
    print('sensor calibration: ',status)
    print('calibrate the gyro. Leave still')
    status = proxy.calibrate_sensor('gyro')
    print('calibrate the accelerometers. Move in 45 degree increments')
    status = proxy.calibrate_sensor('accel')
    print ('calibrate the magnetometer. move at random')
    status = proxy.calibrate_sensor('magnet')
    print('System calibration status: ', status)
    
    # Start mimicing
    run = False
    key = input('enter 1 to start')
    print(key)
    if key == '1':
        run = True
    while run == True:
        # Update orientation
        orientation = proxy.read_sensor()
        print('quaternion: {}'.format(orientation))
      
        ## Convert to Euler angles in radians
        roll_r, pitch_r, yaw_r = quat2euler(orientation)
        
        # Convert to degrees
        roll = math.degrees(roll_r)
        pitch = math.degrees(pitch_r)
        yaw = math.degrees(yaw_r)
        
        if yaw > 180.: yaw = yaw -360.
        if yaw > 45.: yaw = 45.
        if yaw < -45.: yaw = -45.
        if roll > 45.: roll = 45.
        if roll < -45.: roll = -45.
        if pitch > 45.: pitch = 45.
        if pitch < -45.: pitch = -45.
        roll = -roll
        roll_cmd = int(angle_conversion(roll, 'tilt'))
        pitch_cmd = int(angle_conversion(pitch, 'nod'))
        yaw_cmd = int(angle_conversion(yaw, 'pan'))
        
    # send commands to servo controller
        controller.setTarget(motion_codes['tilt'], roll_cmd)
        controller.setTarget(motion_codes['nod'], pitch_cmd)
        controller.setTarget(motion_codes['pan'], yaw_cmd)
        time.sleep(0.01)   # sleep for 1/10 of a second. Update to tune performance
    
        ## test code, remove for final    
        #print sensor.rel_phi, sensor.rel_theta, sensor.rel_psi
        print('roll:',roll)
        print(roll_cmd)
        print('pitch:',pitch)
        print(pitch_cmd)
        print('yaw:',yaw)
        print(yaw_cmd)
    
        #time.sleep(3)

Then you just define the calls that the Pi controlling the skull may send. In my case:

# calibration (sys, gyro, accel, mag)
def calibrate_sensor(sensor_type):
    """Walks user through the calibration process"""
    if sensor == "gyro":
        while sensor.calibration_status[1] < 3:
            time.sleep(1)
    elif sensor_type == 'accel':  
        while sensor.calibration_status[2] < 3:
            time.sleep(1)
    else:    
        while sensor.calibration_status[3] < 3:
            time.sleep(1)
    return (sensor.calibration_status)

def get_calibration_status():
    """Returns the calibration status sys, gyro, accel, mag)"""
    status = sensor.calibration_status
    return status
    
def read_sensor():
    """Returns the orientation in quaternion form"""
    quat = sensor.quaternion
    return quat

server = SimpleXMLRPCServer(('192.168.88.184', 8000)) 
server.register_introspection_functions()
server.register_function(calibrate_sensor)
server.register_function(read_sensor)
server.register_function(get_calibration_status)
try:
    print('Use CTRL-C to exit')
    print('Listening on port 8000...')
    server.serve_forever()
except KeyboardInterrupt:
    print('Keyboard interrup received. Exiting')

It's been a couple of years since I looked at or ran this code, but I hope the code fragments help. xmlrpc is pretty simple to use, and while it has security concerns, this wasn't something I worried about for my skull on my home WiFi.

@srvoets
Copy link
Author

srvoets commented Oct 16, 2024

I used xmlrpc over wifi to send sensor positions to the pi that controls the skull, and then that pi converted the senor readings to commands that were sent to the Maestro servo controller. But you could certainly send servo commands straight from the receiving Pi. The receiving pi just uses the xmlrpc.client library ( in particular, xmlrpc.client.ServerProxy) to send whatever function calls you've defined on the server end.

The server end imports:

from xmlrpc.server import SimpleXMLRPCServer
import xmlrpc.client

xmlrpc just sends the function calls over WiFi to the server side:

# calibration (sys, gyro, accel, mag)
with xmlrpc.client.ServerProxy("http://pi0wsensor:8000") as proxy:
    status = proxy.get_calibration_status()
    print('sensor calibration: ',status)
    print('calibrate the gyro. Leave still')
    status = proxy.calibrate_sensor('gyro')
    print('calibrate the accelerometers. Move in 45 degree increments')
    status = proxy.calibrate_sensor('accel')
    print ('calibrate the magnetometer. move at random')
    status = proxy.calibrate_sensor('magnet')
    print('System calibration status: ', status)
    
    # Start mimicing
    run = False
    key = input('enter 1 to start')
    print(key)
    if key == '1':
        run = True
    while run == True:
        # Update orientation
        orientation = proxy.read_sensor()
        print('quaternion: {}'.format(orientation))
      
        ## Convert to Euler angles in radians
        roll_r, pitch_r, yaw_r = quat2euler(orientation)
        
        # Convert to degrees
        roll = math.degrees(roll_r)
        pitch = math.degrees(pitch_r)
        yaw = math.degrees(yaw_r)
        
        if yaw > 180.: yaw = yaw -360.
        if yaw > 45.: yaw = 45.
        if yaw < -45.: yaw = -45.
        if roll > 45.: roll = 45.
        if roll < -45.: roll = -45.
        if pitch > 45.: pitch = 45.
        if pitch < -45.: pitch = -45.
        roll = -roll
        roll_cmd = int(angle_conversion(roll, 'tilt'))
        pitch_cmd = int(angle_conversion(pitch, 'nod'))
        yaw_cmd = int(angle_conversion(yaw, 'pan'))
        
    # send commands to servo controller
        controller.setTarget(motion_codes['tilt'], roll_cmd)
        controller.setTarget(motion_codes['nod'], pitch_cmd)
        controller.setTarget(motion_codes['pan'], yaw_cmd)
        time.sleep(0.01)   # sleep for 1/10 of a second. Update to tune performance
    
        ## test code, remove for final    
        #print sensor.rel_phi, sensor.rel_theta, sensor.rel_psi
        print('roll:',roll)
        print(roll_cmd)
        print('pitch:',pitch)
        print(pitch_cmd)
        print('yaw:',yaw)
        print(yaw_cmd)
    
        #time.sleep(3)

Then you just define the calls that the Pi controlling the skull may send. In my case:

# calibration (sys, gyro, accel, mag)
def calibrate_sensor(sensor_type):
    """Walks user through the calibration process"""
    if sensor == "gyro":
        while sensor.calibration_status[1] < 3:
            time.sleep(1)
    elif sensor_type == 'accel':  
        while sensor.calibration_status[2] < 3:
            time.sleep(1)
    else:    
        while sensor.calibration_status[3] < 3:
            time.sleep(1)
    return (sensor.calibration_status)

def get_calibration_status():
    """Returns the calibration status sys, gyro, accel, mag)"""
    status = sensor.calibration_status
    return status
    
def read_sensor():
    """Returns the orientation in quaternion form"""
    quat = sensor.quaternion
    return quat

server = SimpleXMLRPCServer(('192.168.88.184', 8000)) 
server.register_introspection_functions()
server.register_function(calibrate_sensor)
server.register_function(read_sensor)
server.register_function(get_calibration_status)
try:
    print('Use CTRL-C to exit')
    print('Listening on port 8000...')
    server.serve_forever()
except KeyboardInterrupt:
    print('Keyboard interrup received. Exiting')

It's been a couple of years since I looked at or ran this code, but I hope the code fragments help. xmlrpc is pretty simple to use, and while it has security concerns, this wasn't something I worried about for my skull on my home WiFi.

thank you for the help, I am going to see if I can somehow get this going today.

@ViennaMike
Copy link
Owner

Were you able to get something going? Any other questions or follow up, or should I mark this issue closed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants