Source code for frc_rekt.motor

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""Model of an frc Motor.

Models an frc motor. Uses data from motors.vex.com.

"""

import logging
import pandas as pd
import numpy as np

from frc_rekt.helpers import get_file_encoding, plot_func

# Pandas options
pd.set_option('max_rows', 121)
pd.set_option('max_columns', 132)
pd.set_option('expand_frame_repr', False)

# just a convenience, so we dont have to type np.poly.poly
POLY = np.polynomial.polynomial


[docs]class Motor(object): # pylint: disable=too-many-instance-attributes,too-few-public-methods """Models a motor.""" motor_types = ['cim', 'mini-cim', '775pro', 'bag'] _stall_voltages = [2, 4, 6, 8, 10, 12] _motor_curve_voltage = 12.0 def __init__(self, motor_type='cim', speed=0.0, voltage=0.0): """Motor. :param motor_type: The type of motor to model :type motor_type: str :param speed: The speed the motor is turning at :type speed: float :param voltage: The voltage being supplied to the motor :type voltage: float """ self._logger = logging.getLogger(__name__) self.motor_type = motor_type self.speed = speed self.voltage = voltage self.curve_frame = self._get_curve_frame() self.stall_frames = self._get_stall_frames() self._generate_functions() self._logger.debug('%s Motor created', self.motor_type) def _get_file_name(self, voltage=None): data_folder = 'data/vex' curve_data_date = { 'cim': '20151104', 'mini-cim': '20151207', '775pro': '20151208', 'bag': '20151207' } stall_data_date = { 'cim': '20151104', 'mini-cim': '20151209', '775pro': '20151209', 'bag': '20151207' } date = curve_data_date[self.motor_type] data_type = 'motor-curve-data' if voltage: data_type = 'locked-rotor-data-{voltage}v'.format(voltage=voltage) date = stall_data_date[self.motor_type] file_name = '{motor_type}-{data_type}-{date}.csv'.format( motor_type=self.motor_type, data_type=data_type, date=date) file_path = '{data_folder}/{motor_type}/{file_name}'.format( data_folder=data_folder, motor_type=self.motor_type, file_name=file_name) encoding = get_file_encoding(file_path) logging.debug('file_path: %s, encoding: %s', file_path, encoding) return file_path, encoding def _get_curve_frame(self): file_path, encoding = self._get_file_name() self._logger.debug('Opening curve: %s', file_path) curve_frame = pd.DataFrame( pd.read_csv(file_path, encoding=encoding) ) # The cast to DataFrame is due to bug: https://github.com/PyCQA/pylint/issues/1161 self._logger.debug('Opened Curve: %s', curve_frame) # Rename columns curve_frame.rename( columns={ 'Speed (RPM)': 'speed', 'Torque (N·m)': 'torque', 'Current (A)': 'current', 'Supplied Power (W)': 'supplied_power', 'Output Power (W)': 'output_power', 'Efficiency (%)': 'efficiency', 'Power Dissipation (W)': 'power_dissipation' }, inplace=True) # Convert to si units curve_frame['speed'] = curve_frame[ 'speed'] / 60.0 # revolutions / second curve_frame['efficiency'] = curve_frame[ 'efficiency'] / 100.0 # percentage scaled to 1 self._logger.debug('Motor Curve: %s', curve_frame) return curve_frame def _get_stall_frames(self): stall_frames = {} for voltage in self._stall_voltages: file_path, encoding = self._get_file_name(voltage=voltage) stall_frame = pd.DataFrame( pd.read_csv(file_path, encoding=encoding) ) # The cast to DataFrame is due to bug: https://github.com/PyCQA/pylint/issues/1161 # rename columns stall_frame.rename( columns={ 'Time': 'time', 'Time (s)': 'time', 'Amps': 'current', 'Current (A)': 'current', 'Volts': 'voltage', 'Voltage (V)': 'voltage', 'Torque 2V (N · m)': 'torque', 'Torque 4V (N · m)': 'torque', 'Torque 6V (N · m)': 'torque', 'Torque 8V (N · m)': 'torque', 'Torque 10V (N · m)': 'torque', 'Torque 12V (N · m)': 'torque' }, inplace=True) stall_frames[voltage] = stall_frame self._logger.debug('Stall frames: %s', stall_frames) return stall_frames def _generate_functions(self): self.current_func = self._generate_basic_function('current') self.torque_func = self._generate_basic_function('torque') self.voltage_scaled_current = self._gen_voltage_scaled_func('current') self.voltage_scaled_torque = self._gen_voltage_scaled_func('torque') def _generate_basic_function(self, y_label, plot=False): x = self.curve_frame['speed'].values y = self.curve_frame[y_label].values coefs = POLY.polyfit(x=x, y=y, deg=1) current_func = POLY.Polynomial(coefs) if plot: plot_func(self.curve_frame, current_func, 'speed', y_label, self.motor_type) return current_func def _choose_stall_indexes(self): time = [0] current = [0] voltage = [0] torque = [0] test_voltage = [0] # Get the first 10 values, picked 10 after looking at # 775pro 12v locked rotor test data # Then, we get the max power in those first 10 data points for test_v in self._stall_voltages: head = self.stall_frames[test_v].iloc[1:10] max_power_index = 0 max_power = 0 for index, row in head.iterrows(): power = row['current'] * row['voltage'] if power > max_power: max_power_index = index max_power = power test_voltage.append(test_v) time.append( self.stall_frames[test_v].iloc[max_power_index]['time']) current.append( self.stall_frames[test_v].iloc[max_power_index]['current']) voltage.append( self.stall_frames[test_v].iloc[max_power_index]['voltage']) torque.append( self.stall_frames[test_v].iloc[max_power_index]['torque']) stall_index = { 'test_voltage': test_voltage, 'time': time, 'current': current, 'voltage': voltage, 'torque': torque } return pd.DataFrame(stall_index) def _gen_voltage_scaled_func(self, y_label, plot=False): percent_label = '{0}_percent'.format(y_label) stall_df = self._choose_stall_indexes() y_label_12v = stall_df[y_label].iloc[6] stall_df[percent_label] = stall_df[y_label] / y_label_12v x = stall_df['voltage'] y = stall_df[percent_label] coefs = POLY.polyfit( x=x, y=y, deg=[1, 2, 3]) # Don't use the 0th term because we want to intercept 0,0 vs_func = POLY.Polynomial(coefs) if plot: predict_df = [{'voltage': 13}, {'voltage': 14}] stall_df = stall_df.append(predict_df, ignore_index=True) plot_func(stall_df, vs_func, 'voltage', percent_label, self.motor_type) return vs_func