You are here

PyVESC - Realtime Data - Experiment Export .csv file

4 posts / 0 new
Last post
Eric_IraLo
Offline
Last seen: 4 months 1 week ago
VESC Free
Joined: 2024-01-29 22:17
Posts: 7
PyVESC - Realtime Data - Experiment Export .csv file

Good afternoon everyone! 

I hope that you are doing great wherever in the world you might be. 

 

After getting more and more familiar with the VESC software and also some Python incorporation that can be done with the PyVESC module, I am trying to build a project where I run both of the motors that I purchased previously from Flipsky, "63100 170KV 5500W" & "7070 110kv". 

 

My main goal of the experiment, is that I connected both of them mechanically, and having in between a gearbox on 9:1 (to increase RPM and get more torque), so when one of them spins, the other one does too. But once the 'Driver' motor reaches certain RPM, the 'Generator' Motor starts braking and in this case doing some regenerative-braking to see how much power can be generated. 

 

My issue so far has been that the connection with Python and VESC is not that stable, but also NO-DATA is being logged or stored to be latter exported in a.csv file. 

I am currently trying to build a code in Python, I am attaching what I currently have so far. 

 

In this code, the Driver Motor is able to start, but I do not see the Gen Motor starting to brake in steps. Then when both motors finish, I always get the error that says that the Error is that there ir NO-DATA to be able to be exported as a separate file. I still havent't filtered the data, but my first goal is to just see data being exported somehow.

 

Thank you so much for you help and I am looking forward to maybe start a conversation on how to solve this issue.

 

Thanks again.

 

 

My most updated code as of today (March, Wednesday 13th, 2024);

# Import necessary libraries

from threading import Thread

import csv

import time

import math

import numpy as np

import matplotlib.pyplot as plt

from pyvesc import VESC

 

# Motor parameters and constants

gearbox_ratio = 9

rolling_radii_TW_IN = 1.25

rolling_radii_eGen_IN = 1.25

experiment_duration = 20

driver_pole_pairs = 7

gen_pole_pairs = 7

ms = 0.01                           #Minimum-Sleep time

 

# Initialize the VESC communication

driverVESC = VESC("COM9", False, True, 115200, ms)   # Replace/Double-check with the correct serial port for your DriverVESC

genVESC  =   VESC("COM16", False, True, 115200, ms)  # Replace/Double-check with the correct serial port for your GenVESC

 

# Collect data for both motors

driver_data = []

gen_data = []

 

target_speed_mph = float(input("Enter the target speed (MPH) for the Driver Motor: "))

 

# Function to calculate the target_speeds_mph into RPMs for DriverVESC

def mph_to_erpm_driverVESC(mph):

   

    #Calculate the circumference (rolling radii * 2π)

    circumference_inches = 2 * math.pi * rolling_radii_TW_IN

 

    #Convert Miles per Hour to inches per minute (1 mile = 63,360 in)

    inches_per_minute = (float(mph) * 63360) / 60  

 

    #Convert to RPM dividing the inches_per_minute by the circumference of the wheel.

    rpm = inches_per_minute / circumference_inches

 

    #Calculate the RPM (ERPM) output of the motor, considering the pole pairs

    erpm = rpm * driver_pole_pairs

 

    return erpm

 

# Function to calculate the MPH from the RPM for DriverVESC

def erpm_to_mph_driverVESC(erpm):

 

    #Calculate the circumference (rolling radii * 2π)

    circumference_inches = 2 * math.pi * rolling_radii_TW_IN

 

    #Calculate the RPM (ERPM) output of the motor, considering the pole pairs

    rpm = erpm / driver_pole_pairs

 

    #Convert RPM to inches per minute taking into acount the circumference of the wheel.

    inches_per_minute = (rpm*circumference_inches)

 

    #Convert to MPH by multipling the inches_per_minute by 60 minutes of an hour over, (1 mile = 63,360 in).

    mph = (float(inches_per_minute) * 60) / 63,360

 

    return mph

 

#Function to calculate the MPH from the eGen Motor

def rpm_to_mph_genVESC(erpm):

   

    #Calculate the circumference (rolling radii * 2π)

    circumference_inches = 2 * math.pi * rolling_radii_eGen_IN

 

    #Calculate the RPM (ERPM) output of the motor, considering the pole pairs

    rpm = erpm / gen_pole_pairs

 

    #Convert RPM to inches per minute taking into acount the circumference of the wheel.

    inches_per_minute = (rpm*circumference_inches)

 

    #Convert to MPH by multipling the inches_per_minute by 60 minutes of an hour over, (1 mile = 63,360 in).

    mph = (float(inches_per_minute) * 60) / 63,360

 

    return mph

 

# Function to control regenerative braking for GenVESC

def regenerative_braking():

    epsilon = 2                 #Small value to account for floating-point comparison, this would check for a difference of less than 2 MPH

    Brake_Current = 6           #The motor should start free-spinning applying no constrain to the set-speed of the driver.

    Brake_Step = 0.2            #How much the Brake will step, or increase, every 5 seconds.

   

    # Start DriverVESC at saved target speeds (MPH)

    if time.time() % 5 == 0 and abs(target_speed_mph - erpm_to_mph_driverVESC(driverVESC.get_rpm())) < epsilon:

        Brake_Current += Brake_Step

        genVESC.set_current(Brake_Current)

        time.sleep(ms)

 

# Stop both motors by setting their current to 0,

def stop_motors():

    print("Stopping both motors.")

    driverVESC.set_current(0)

    genVESC.set_current(0)

 

# Function to collect data from driverVESC

def time_get_values_D():

    start = time.time()

    while time.time() - start < experiment_duration:

        driver_data.append(driverVESC.get_measurements())

        time.sleep(ms)  

    return driver_data

 

# Function to collect data from genVESC

def time_get_values_E():

    start = time.time()

    while time.time() - start < experiment_duration:    

        gen_data.append(genVESC.get_measurements())

        time.sleep(ms)  

    return gen_data

 

# Function to save data to CSV file

def save_to_csv(data, motor_name):

    filename = f"{motor_name.lower()}_data.csv"

    with open(filename, "w", newline="") as csvfile:

        writer = csv.writer(csvfile)

        writer.writerow(["Time (s)", "RPM", "Power (W)", "Amps (A)"])

        writer.writerows(data)

    print(f"Data for {motor_name} saved to {filename}")

 

# Function to plot comparison graphs

def plot_comparison_graphs(driver_data, gen_data):

    driver_data = np.array(driver_data)

    gen_data = np.array(gen_data)

 

    plt.figure(figsize=(10, 6))

 

    plt.subplot(3, 1, 1)

    plt.plot(driver_data[:, 0], driver_data[:, 2], label="DriverVESC")

    plt.plot(gen_data[:, 0], gen_data[:, 2], label="GenVESC")

    plt.xlabel("Time (s)")

    plt.ylabel("Power (W)")

    plt.legend()

 

    plt.subplot(3, 1, 2)

    plt.plot(driver_data[:, 0], driver_data[:, 3], label="DriverVESC")

    plt.plot(gen_data[:, 0], gen_data[:, 3], label="GenVESC")

    plt.xlabel("Time (s)")

    plt.ylabel("Amps (A)")

    plt.legend()

 

    plt.subplot(3, 1, 3)

    plt.plot(driver_data[:, 0], driver_data[:, 1], label="DriverVESC")

    plt.plot(gen_data[:, 0], gen_data[:, 1], label="GenVESC")

    plt.xlabel("Time (s)")

    plt.ylabel("RPM")

    plt.legend()

 

    plt.tight_layout()

    plt.show()

 

# Main function

def main():

    # Start DriverVESC at saved target speed (MPH)

    print(f"Starting DriverVESC at {target_speed_mph} MPH.")

    str_speed = str(mph_to_erpm_driverVESC(target_speed_mph))

 

    #Get the value of the ERPMs and the RPMs.

    erpm_setpoint = math.ceil(float(str_speed))

    rpm_setpoint = erpm_setpoint / driver_pole_pairs

   

    driverVESC.set_rpm(int(erpm_setpoint))

    print(f"Which is translated to: {erpm_setpoint} eRPMs." )

    print(f"Which is also translated to: {rpm_setpoint} RPMs." )

    time.sleep(30)

 

    # Stop motors after completing the experiment

    stop_motors()

 

    # Clean up (close the connection)

    if genVESC.__exit__ and driverVESC.__exit__:

        print("Serials closed")

        VESC.__exit__

        exit()

 

    # Save data to CSV files

    save_to_csv(driver_data, "driverVESC")

    save_to_csv(gen_data, "genVESC")

 

    # Plot comparison graphs

    plot_comparison_graphs(driver_data, gen_data)

 

if __name__ == "__main__":

    Thread(target = main).start()

    Thread(target = time_get_values_D).start()

    Thread(target = time_get_values_E).start()

    Thread(target = regenerative_braking).start()

 

Klukec
Offline
Last seen: 3 months 3 weeks ago
VESC Free
Joined: 2024-02-11 19:55
Posts: 2

I took a look at your code and the PyVesc driver. There are some bugs in the VESC class because of some different atribute names.

You said that your connection is not very stable. I don't exactly know if you are having problems with reading data from VESC but I would suggest that you increase the port timeout. Currently you have It set to ms = 0.01, but I would change it to 0.5 seconds (unless you changed timeout in app settings). If the port timeouts then "VESC.get_measurements()" returns None object.

 

The problem that your generator is not breaking in because you are using "genVESC.set_current(Brake_Current)" method. This method sets the current of the motor and not the break current. Since you are setting the current the motor acts as driver, not generator. If you would to put in a negative current (opposite spinning direction) I don't know what would happen but I would not try it. To set the breaking current a different message must be sent to VESC than the set current message. PyVesc has support for the breaking current message, but it is not implemented in VESC class. You can add  the following method to VESC class:

def set_brake_current(self, brake_current):
        """
        :param brake_current: brake current in amps for the motor
        """
        self.write(encode(SetCurrentBrake(brake_current)))

or you can use the write method of the VESC class so you don't have to modify the class (but you also need to include the SetCurrentBrake message) :

from pyvesc.VESC.messages import SetCurrentBrake
genVESC.write(encode(SetCurrentBrake(brake_current)))

 

The problem that you can't save your data to csv is because the "VESC.get_measurements()" does not return a array or a tuple of values, but instead a class instance, so you have to access the values via attribures. The class has the folowing attribures:

temp_fet
temp_motor
avg_motor_current
avg_input_current
avg_id
avg_iq
duty_cycle_now
rpm
v_in
amp_hours
amp_hours_charged
watt_hours
watt_hours_charged
tachometer
tachometer_abs
mc_fault_code
pid_pos_now
app_controller_id

So you can get any value from the class by calling data.rpm or data.avg_motor_current or data.v_in ...

You couldn't store the values to csv because you were trying to stor a object instance which is not iterable.

 

There a are also bugs in the VESC class because of different attribute names which brakes the "get_duty_cycle(), get_motor_current(), get_incoming_current()" methods. Take a look at them and you will see that the attribute names don't match the above ones. So change them the ones I listed.

Maybe If I will have some time I will make a pull request to fix the bugs, but don't hold me to my world.

 

 

Klukec
Offline
Last seen: 3 months 3 weeks ago
VESC Free
Joined: 2024-02-11 19:55
Posts: 2

Oh and I forgot to add. I also once had a simular setup where I had a driver motor and a generator motor which were the same type and coupled directly with no gear box. The generator motor gave out about 2/3 of the power driver motor so the efficiency was around 66%.

Eric_IraLo
Offline
Last seen: 4 months 1 week ago
VESC Free
Joined: 2024-01-29 22:17
Posts: 7

Hi Klukec!

 

Thank you so much for your feedback! 

 

I will take a closer look at your comments and make the respective changes so that I can update you on how it went! 

 

Once again I really appreciate all of your comments and support! 

 

Best wishes!!

Eric Irabien Lozano