I am trying to send data from an Arduino into Python via USB every 100 ms. What improvements can be made to my code? I feel like it is kind of bloated and can be better written, but I'm not sure which improvements should be made.
read_arduino.py
import serial
def _has_digits(string):
return any(char.isdigit() for char in str(string))
def _check_for_overflowed_digits(string):
if _has_digits(string):
overflowed_digits = [ char for char in string.split() if char.isdigit() ]
digits_to_string = ("").join(overflowed_digits)
return digits_to_string
else:
return None
def _compute_state_vector(data_string):
data_array = data_string.split(",")
state_vector = {
'pendulum_angle': data_array[0],
'cart_position': data_array[1],
'pendulum_angular_velocity': data_array[2],
# Sometimes the carriage return "\r" remains in the string, so get rid of it
'cart_velocity': (data_array[3] if '\r' not in data_array[3] else data_array[3].split('\r')[0])
}
return state_vector
def _compute_input(state):
state_vector = [state['pendulum_angle'], state['cart_position'], state['pendulum_angular_velocity'], state['cart_velocity']]
gain_matrix = np.array([
[1000, 0, 0, 0],
[0, 10, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1],
])
control_input = -gain_matrix * state_vector
return control_input
class Arduino():
def __init__(self, port, baud_rate, timeout):
self.serial = serial.Serial(port, baud_rate, timeout = timeout)
def start_control(self):
last_line_recieved = ""
try:
while True:
serial_bytes = self.serial.readline()
decoded_line = str(serial_bytes.decode('utf8'))
# Read from serial until we capture the whole line of data
if (len(serial_bytes) != 0) and (b'\n' in serial_bytes):
# Sometimes digits overflow from one line onto the next
overflowed_digits = _check_for_overflowed_digits(decoded_line)
if overflowed_digits is not None:
last_line_recieved += overflowed_digits
# Parse data into state_vector and compute control input using LQR
state_vector = _compute_state_vector(last_line_recieved)
control_input = compute_input(state_vector)
print(control_input)
# Reset the variable
last_line_recieved = ""
elif len(serial_bytes) != 0:
last_line_recieved += decoded_line
else:
pass
except KeyboardInterrupt:
print("You have quit reading from the serial port.")
pass
if __name__ == "__main__":
arduino = Arduino("/dev/cu.usbmodem14101", baud_rate = 9400, timeout = 0)
arduino.start_control()
main.cpp
#define ENCODER_OPTIMIZE_INTERRUPTS
#include <Arduino.h>
#include <Encoder.h>
#include "motorControllerDrokL298.h"
#include "pythonUtils.h"
// Initialize encoders
#define cartEncoderPhaseA 3
#define cartEncoderPhaseB 4
#define pendulumEncoderPhaseA 2
#define pendulumEncoderPhaseB 5
Encoder cartEncoder(cartEncoderPhaseA, cartEncoderPhaseB);
Encoder pendulumEncoder(pendulumEncoderPhaseA, pendulumEncoderPhaseB);
// Initialize named constants
const unsigned long TIMEFRAME = 100; // milliseconds
const double ENCODER_PPR = 2400.0;
// Initialize variables
unsigned long previousMilliseconds = 0;
float previousCartPosition = 0;
float previousPendulumAngle = 0;
void setup()
{
Serial.begin(9400);
// Motor controller
pinMode(IN1, OUTPUT);
pinMode(IN2, OUTPUT);
pinMode(ENA, OUTPUT);
}
void loop()
{
unsigned long currentMilliseconds = millis();
long cartEncoderCount = cartEncoder.read();
long pendulumEncoderCount = pendulumEncoder.read();
// Send values to python every n milliseconds
if ( (currentMilliseconds - previousMilliseconds) > TIMEFRAME ) {
// Compute the state
stateVector state;
state.pendulumAngle = encoderCountToAngleRadians(pendulumEncoderCount, ENCODER_PPR); // radians
state.cartPosition = encoderCountToCartPositionInches(cartEncoderCount, ENCODER_PPR); // in
state.pendulumAngularVelocity = (state.pendulumAngle - previousPendulumAngle)/(TIMEFRAME/1000.0); // radians/s
state.cartVelocity = (state.cartPosition - previousCartPosition)/(TIMEFRAME/1000.0); // in/s
sendStateVectorToPython(state);
// Store the current data for computation in the next loop
previousMilliseconds = millis();
previousPendulumAngle = state.pendulumAngle;
previousCartPosition = state.cartPosition;
}
}
pythonUtils.h
#ifndef PYTHON_UTILS
#define PYTHON_UTILS
#include <Arduino.h>
struct stateVector
{
double pendulumAngle;
double cartPosition;
double pendulumAngularVelocity = 6.0;
double cartVelocity = 5.0;
};
float encoderCountToAngleDegrees(long encoderCount);
float encoderCountToCartPositionInches(long cartEncoderCount, double encoderPPR);
void sendStateVectorToPython(stateVector state);
float encoderCountToAngleRadians(long encoderCount, double encoderPPR)
{
return (encoderCount / encoderPPR) * (2.0 * PI);
}
float encoderCountToCartPositionInches(long cartEncoderCount, double encoderPPR) {
float idlerPulleyRadius = 0.189; // inches
float cartAngle = encoderCountToAngleRadians(cartEncoderCount, encoderPPR); // radians
return idlerPulleyRadius * cartAngle;
}
void sendStateVectorToPython(stateVector state)
{
Serial.print(state.pendulumAngle);
Serial.print(",");
Serial.print(state.cartPosition);
Serial.print(",");
Serial.print(state.pendulumAngularVelocity);
Serial.print(",");
Serial.print(state.cartVelocity);
Serial.println();
}
#endif
```