Commit c8c4d501 authored by Chetan Sharma's avatar Chetan Sharma
Browse files

Fixed some sensor issues, model issues

parent 113d8b1d
...@@ -3,43 +3,9 @@ ...@@ -3,43 +3,9 @@
*.unfinished* *.unfinished*
private/ private/
.vscode/ .vscode/
saved_cuts/
*~$* *~$*
# C++ Files
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# Python Files
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
...@@ -62,7 +28,6 @@ parts/ ...@@ -62,7 +28,6 @@ parts/
sdist/ sdist/
var/ var/
wheels/ wheels/
pip-wheel-metadata/
share/python-wheels/ share/python-wheels/
*.egg-info/ *.egg-info/
.installed.cfg .installed.cfg
...@@ -92,6 +57,7 @@ coverage.xml ...@@ -92,6 +57,7 @@ coverage.xml
*.py,cover *.py,cover
.hypothesis/ .hypothesis/
.pytest_cache/ .pytest_cache/
cover/
# Translations # Translations
*.mo *.mo
...@@ -114,6 +80,7 @@ instance/ ...@@ -114,6 +80,7 @@ instance/
docs/_build/ docs/_build/
# PyBuilder # PyBuilder
.pybuilder/
target/ target/
# Jupyter Notebook # Jupyter Notebook
...@@ -124,7 +91,9 @@ profile_default/ ...@@ -124,7 +91,9 @@ profile_default/
ipython_config.py ipython_config.py
# pyenv # pyenv
.python-version # For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv # pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
...@@ -169,3 +138,9 @@ dmypy.json ...@@ -169,3 +138,9 @@ dmypy.json
# Pyre type checker # Pyre type checker
.pyre/ .pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
\ No newline at end of file
...@@ -8,6 +8,10 @@ The current goal of this project is to make a system that faces a material using ...@@ -8,6 +8,10 @@ The current goal of this project is to make a system that faces a material using
## Modeling ## Modeling
Models for forces experienced during the cutting process and models for tool / machine failure are in [software/models.py](software/models.py). Models for forces experienced during the cutting process and models for tool / machine failure are in [software/models.py](software/models.py).
The linear model converges well when given test data sweeps.
![](assets/model_converge.png)
## Hardware ## Hardware
The hardware setup is finished. The machine is a Taig Micro Mill (kindly donated by Ted Hall). The hardware setup is finished. The machine is a Taig Micro Mill (kindly donated by Ted Hall).
...@@ -27,3 +31,5 @@ The machine electronics are enclosed for safety. ...@@ -27,3 +31,5 @@ The machine electronics are enclosed for safety.
## Software ## Software
The machine controller is in the [software](software/) folder. The machine controller is in the [software](software/) folder.
...@@ -15,7 +15,7 @@ void loop() { ...@@ -15,7 +15,7 @@ void loop() {
if (Serial.available() > 0) { if (Serial.available() > 0) {
char inByte = Serial.read(); char inByte = Serial.read();
if (inByte == 'F') { if (inByte == 'F') {
float weight = loadcell.get_value(); float weight = -loadcell.get_value();
Serial.println(weight); Serial.println(weight);
} else if (inByte == 'T') { } else if (inByte == 'T') {
loadcell.tare(); loadcell.tare();
......
"""
Intended to be the brain box of this project; for now, it just runs a test sweep to collect initial data.
"""
import numpy as np import numpy as np
import time
from objects import EndMill, Conditions, MachineChar from objects import EndMill, Conditions, MachineChar
from cut import Cut from cut import Cut
...@@ -7,3 +11,25 @@ from optimize import Optimizer ...@@ -7,3 +11,25 @@ from optimize import Optimizer
import logging import logging
logging.basicConfig(level="INFO") logging.basicConfig(level="INFO")
MACHINE_PORT = '/dev/ttyS25'
SPINDLE_PORT = '/dev/ttyS33'
TFD_PORT = '/dev/ttyS35'
endmill = EndMill(3, 3.175e-3, 3.175e-3, 19.05e-3, 1e-3)
cut = Cut(MACHINE_PORT, SPINDLE_PORT, TFD_PORT, endmill, 80e-3, 50.8e-3, 5e-3, 300, save_as = "6061-sweep")
f_r_range = np.linspace(2e-3, 5e-3, 9)
W_range = np.linspace(1e-3, 3.175e-3, 10)
cut.face_layer(D = 0.3e-3)
cut.begin_layer(D = 1e-3)
for f_r in f_r_range:
for W in W_range:
conditions = Conditions(1e-3, W, f_r, 300, endmill)
cut.cut(conditions, save = True, auto_layer=True)
cut.close()
\ No newline at end of file
...@@ -16,8 +16,10 @@ class MachineCrash(Exception): ...@@ -16,8 +16,10 @@ class MachineCrash(Exception):
class Cut: class Cut:
def __init__(self, machine_port, spindle_port, tfd_port, endmill, x_max, y_max, initial_z=0, save_as=None): def __init__(self, machine_port, spindle_port, tfd_port, endmill, x_max, y_max, f_r_clearing, w_clearing, initial_z=0, save_as=None):
self.machine = Machine(machine_port) self.machine = Machine(machine_port)
self.machine.unlock()
self.machine.zero()
self.spindle = Spindle_Applied(spindle_port) self.spindle = Spindle_Applied(spindle_port)
self.tfd = TFD(tfd_port) self.tfd = TFD(tfd_port)
log.info("Initialized all systems.") log.info("Initialized all systems.")
...@@ -27,44 +29,92 @@ class Cut: ...@@ -27,44 +29,92 @@ class Cut:
self.x_max = x_max self.x_max = x_max
self.y_max = y_max self.y_max = y_max
self.cut_x = 0 self.cut_x = 0
self.cut_z = initial_z self.cut_z = -initial_z # initial z in positive units
self.D = 0 self.D = 0
self.f_r_clearing = f_r_clearing
self.w_clearing = w_clearing
self.save_as = save_as self.save_as = save_as
# deriving some constants # deriving some constants
self.Y_START = - 2 * endmill.R self.Y_START = - 2 * endmill.r_c
self.Y_END = y_max + 2 * endmill.R self.Y_END = y_max + 2 * endmill.r_c
self.X_END = x_max - endmill.R self.X_START = endmill.r_c
self.Y_DATA_START = 1.5 * endmill.R self.X_END = x_max - endmill.r_c
self.Y_DATA_END = y_max - 1.5 * endmill.R self.Y_DATA_START = 1.5 * endmill.r_c
self.Y_DATA_END = y_max - 1.5 * endmill.r_c
def __del__(self): def __del__(self):
self.spindle.set_w(0) self.spindle.set_w(0)
def begin_layer(self, D, f_r_clearing, w_clearing): def warmup_spindle(self):
log.info("Warming up spindle")
self.spindle.set_w(300)
time.sleep(30)
self.spindle.set_w(0)
log.info("Finished warming spindle")
def face_layer(self, D):
""" """
Prepares to start facing with cut depth D. Successive calls will compensate for previous cut depths. Faces a layer to ensure that all cuts afterwards are even.
Args: Args:
D: Depth of cut for this layer. D: Depth of cut for this layer.
f_r_clearing: feedrate used for this clearing pass. f_r_clearing: feedrate used for this clearing pass.
w_clearing: spindle speed used for this clearing pass. w_clearing: spindle speed used for this clearing pass.
"""
log.info("Preparing to face layer to depth " + str(D) +
" at feedrate " + str(self.f_r_clearing) + " with speed " + str(self.w_clearing))
# define next cut
self.D = D
self.cut_z -= D
cuts = np.append(np.arange(self.X_START, self.X_END, 1.8 * self.endmill.r_c), self.X_END)
# perform cut
self.spindle.set_w(self.w_clearing)
self.machine.rapid({'x': self.X_START, 'y': self.Y_START})
self.machine.rapid({'z': self.cut_z})
location = 'BOTTOM'
for x in cuts:
self.machine.rapid({'x': x})
if location == 'BOTTOM':
self.machine.cut({'y': self.Y_END}, self.f_r_clearing)
location = 'TOP'
elif location == 'TOP':
self.machine.cut({'y': self.Y_START}, self.f_r_clearing)
location = 'BOTTOM'
self.machine.rapid({'z': self.cut_z + D + 1e-3})
self.machine.rapid({'x': self.X_START, 'y': self.Y_START})
self.machine.rapid({'z': self.cut_z})
self.machine.hold_until_still()
log.info("Workpiece faced")
def begin_layer(self, D):
"""
Prepares to start facing with cut depth D. Successive calls will compensate for previous cut depths.
Args:
D: Depth of cut for this layer (in positive units)
f_r_clearing: feedrate used for this clearing pass.
w_clearing: spindle speed used for this clearing pass.
Returns: Returns:
A data blob from this operation. A data blob from this operation.
""" """
X_START = self.endmill.R
log.info("Preparing to clear layer to depth " + str(D) + log.info("Preparing to clear layer to depth " + str(D) +
" at feedrate " + str(f_r_clearing) + " with speed " + str(w_clearing)) " at feedrate " + str(self.f_r_clearing) + " with speed " + str(self.w_clearing))
self.D = D self.D = D
self.cut_z -= D self.cut_z -= D
self.spindle.set_w(w_clearing) self.spindle.set_w(self.w_clearing)
self.machine.rapid({'x': X_START, 'y': self.Y_START}) self.machine.rapid({'x': self.X_START, 'y': self.Y_START})
self.machine.rapid({'z': self.cut_z}) self.machine.rapid({'z': self.cut_z})
self.machine.cut({'y': self.Y_END}, f_r_clearing) self.machine.cut({'y': self.Y_END}, self.f_r_clearing)
self.machine.rapid({'z': self.cut_z + D + 1}) self.machine.rapid({'z': self.cut_z + D + 1e-3})
self.machine.rapid({'x': X_START, 'y': self.Y_START}) self.machine.rapid({'x': self.X_START, 'y': self.Y_START})
self.machine.rapid({'z': self.cut_z}) self.machine.rapid({'z': self.cut_z})
self.machine.hold_until_still() self.machine.hold_until_still()
...@@ -72,13 +122,34 @@ class Cut: ...@@ -72,13 +122,34 @@ class Cut:
log.info("Layer prepared for clearing") log.info("Layer prepared for clearing")
def cut(self, conditions): def cut(self, conditions, save = True, auto_layer = True):
""" """
Performs a stroke of facing. Returns a data blob. Performs a stroke of facing. Returns a data blob.
""" """
_, W, f_r, w, _ = conditions.unpack() _, W, f_r, w, _ = conditions.unpack()
self.spindle.set_w(w)
X_START = self.x_cut - self.endmill.r_c + W X_START = self.x_cut - self.endmill.r_c + W
if X_START > self.X_END: if X_START > self.X_END:
if auto_layer:
log.info("Hit end of travel, polishing off remaining bits.")
# just finish off the rest of the layer
self.machine.rapid({'x': self.X_END, 'y': self.Y_START})
self.machine.rapid({'z': self.cut_z})
self.machine.cut({'y': self.Y_END}, self.f_r_clearing)
self.machine.rapid({'z': self.cut_z + self.D + 1e-3})
self.machine.hold_until_still()
# start next layer
self.begin_layer(self.D)
log.info("Actually performing cut now.")
# try again, return result
return self.cut(conditions, save = save, auto_layer=False)
else:
raise MachineCrash( raise MachineCrash(
"Cutting too far in X direction: X = " + str(X_START)) "Cutting too far in X direction: X = " + str(X_START))
...@@ -103,7 +174,7 @@ class Cut: ...@@ -103,7 +174,7 @@ class Cut:
lambda state: state['wpos']['y'] > self.Y_DATA_START) lambda state: state['wpos']['y'] > self.Y_DATA_START)
log.info("Beginning data collection") log.info("Beginning data collection")
time_start = time.perf_counter time_start = time.perf_counter()
self.spindle.start_measurement(outQueue, time_start) self.spindle.start_measurement(outQueue, time_start)
self.tfd.start_measurement(outQueue, time_start) self.tfd.start_measurement(outQueue, time_start)
self.machine.hold_until_condition( self.machine.hold_until_condition(
...@@ -114,37 +185,50 @@ class Cut: ...@@ -114,37 +185,50 @@ class Cut:
self.tfd.stop_measurement() self.tfd.stop_measurement()
log.info("Finished cut, collected " + log.info("Finished cut, collected " +
str(len(outQueue)) + " data points.") str(outQueue.qsize()) + " data points.")
self.machine.rapid({'z': self.cut_z + self.D + 1}) self.machine.rapid({'z': self.cut_z + self.D + 1e-3})
self.machine.rapid({'y': self.Y_START}) self.machine.rapid({'y': self.Y_START})
self.machine.hold_until_still()
self.x_cut += W self.x_cut += W
Ts, Fys = self.dump_queue(outQueue) Ts, Fys = self.dump_queue(outQueue)
data = Data(self.D, W, f_r, w, self.endmill, Ts, Fys) data = Data(self.D, W, f_r, w, self.endmill, Ts, Fys)
if self.save_as: if self.save_as and save:
with shelve.open(os.path.join("saved_cuts", "db")) as db: with shelve.open(os.path.join("saved_cuts", "db")) as db:
if self.save_as in db: if self.save_as in db:
db[self.save_as].append(data) existing = db[self.save_as]
existing.append(data)
db[self.save_as] = existing
else: else:
db[self.save_as] = [data] db[self.save_as] = [data]
db.sync()
log.info("Data saved under name " + self.save_as)
return data
def close(self):
log.info("Returning home before closing")
self.machine.rapid({'x': 0, 'y': 0, 'z': 0})
time.sleep(0.1)
self.machine.hold_until_still()
def dump_queue(self, outQueue): def dump_queue(self, outQueue):
Ts, Fys = list(), list() Ts, Fys = list(), list()
while not outQueue.empty(): while not outQueue.empty():
t, datum = outQueue.get() t, datum = outQueue.get()
datum_type, datum_value = datum datum_type, datum_value = datum
if datum_type == 'spindle': if datum_type == 'spindle':
Ts.append(t, datum_value) Ts.append([t, datum_value])
elif datum_type == 'tfd': elif datum_type == 'tfd':
Fys.append(t, datum_value) Fys.append([t, datum_value])
return np.array(Ts), np.array(Fys) return np.array(Ts), np.array(Fys)
if __name__ == "__main__": if __name__ == "__main__":
endmill = EndMill(3, 3.175e-3, 3.175e-3, 0.019, 0.004) log.info("Hi")
cut = Cut('/dev/ttyS28', '/dev/ttyS26', 'dev/ttyS27', endmill, 50e-3, 50e-3)
...@@ -4,7 +4,7 @@ import numpy as np ...@@ -4,7 +4,7 @@ import numpy as np
from sklearn import linear_model from sklearn import linear_model
from matplotlib import pyplot as plt from matplotlib import pyplot as plt
from models import T_lin, F_lin, T_x_vector, Fy_x_vector from models import T_lin, F_lin, T_x_vector, T_x_vector_padded, Fy_x_vector
from objects import Data, Conditions, EndMill, Prediction from objects import Data, Conditions, EndMill, Prediction
...@@ -22,7 +22,8 @@ class Model(abc.ABC): ...@@ -22,7 +22,8 @@ class Model(abc.ABC):
pass pass
def ingest_data(self, data): def ingest_data(self, data):
return list(map(self.ingest_datum, data)) for datum in data:
self.ingest_datum(datum)
@abc.abstractmethod @abc.abstractmethod
def predict_one(self, conditions): def predict_one(self, conditions):
...@@ -32,7 +33,7 @@ class Model(abc.ABC): ...@@ -32,7 +33,7 @@ class Model(abc.ABC):
pass pass
def predict(self, conditions): def predict(self, conditions):
return list(map(conditions, self.predict_one)) return list(map(self.predict_one, conditions))
class LinearModel(Model): class LinearModel(Model):
...@@ -49,7 +50,7 @@ class LinearModel(Model): ...@@ -49,7 +50,7 @@ class LinearModel(Model):
def ingest_datum(self, datum): def ingest_datum(self, datum):
# decompose # decompose
_, _, _, _, _, Ts, Fys = datum.unpack() _, _, _, _, _, Ts, Fys = datum.unpack()
T, Fy = np.median(Ts[1, :]), np.median(Fys[1, :]) T, Fy = np.median(Ts[:, 1]), np.median(Fys[:, 1])
# get linear coefficients # get linear coefficients
T_x = T_x_vector(datum.conditions()) T_x = T_x_vector(datum.conditions())
Fy_x = Fy_x_vector(datum.conditions()) Fy_x = Fy_x_vector(datum.conditions())
...@@ -87,3 +88,100 @@ class LinearModel(Model): ...@@ -87,3 +88,100 @@ class LinearModel(Model):
# repack and return # repack and return
return Prediction(*conditions.unpack(), T, F) return Prediction(*conditions.unpack(), T, F)
class UnifiedLinearModel(Model):
def __init__(self):
self.training_Tx = list()
self.training_Ty = list()
self.training_Fyx = list()
self.training_Fyy = list()
self.regressor = linear_model.LinearRegression(fit_intercept=False)
self.params = np.array([0, 0, 0, 0], dtype='float64')
def ingest_datum(self, datum):
# decompose
_, _, _, _, _, Ts, Fys = datum.unpack()
T, Fy = np.median(Ts[:, 1]), np.median(Fys[:, 1])
# get linear coefficients
T_x = np.array(T_x_vector_padded(datum.conditions()))
Fy_x = np.array(Fy_x_vector(datum.conditions()))
# we want to artificially inflate T to be as big as F
# this is a little arbitrary, might not be the best idea lol
ratio = Fy_x[:2].mean() / T_x[:2].mean()
T_x *= ratio
T *= ratio
# add T to training set
self.training_Tx.append(T_x)
self.training_Ty.append(T)
# add Fy to training set
self.training_Fyx.append(Fy_x)
self.training_Fyy.append(Fy)
self.update()
def update(self):
# calculate best fit from data
self.regressor.fit(self.training_Tx + self.training_Fyx, self.training_Ty + self.training_Fyy)
self.params = np.array(self.regressor.coef_)
def predict_one(self, conditions):
# evaluate
T = T_lin(conditions, *self.params[:2])
F = F_lin(conditions, *self.params)
# repack and return
return Prediction(*conditions.unpack(), T, F)
class RANSACLinearModel(Model):
def __init__(self):
self.training_Tx = list()
self.training_Ty = list()
self.training_Fyx = list()
self.training_Fyy = list()
base_regressor = linear_model.LinearRegression(fit_intercept=False)
self.regressor = linear_model.RANSACRegressor(base_regressor, min_samples=5)
self.params = np.array([0, 0, 0, 0], dtype='float64')
def ingest_datum(self, datum):
# decompose
_, _, _, _, _, Ts, Fys = datum.unpack()
T, Fy = np.median(Ts[:, 1]), np.median(Fys[:, 1])
# get linear coefficients
T_x = np.array(T_x_vector_padded(datum.conditions()))
Fy_x = np.array(Fy_x_vector(datum.conditions()))
# we want to artificially inflate T to be as big as F
# this is a little arbitrary, might not be the best idea lol
ratio = Fy_x[:2].mean() / T_x[:2].mean()
T_x *= ratio
T *= ratio
# add T to training set
self.training_Tx.append(T_x)
self.training_Ty.append(T)
# add Fy to training set
self.training_Fyx.append(Fy_x)
self.training_Fyy.append(Fy)
if (len(self.training_Tx) + len(self.training_Fyx)) > 4:
self.update()
def update(self):