Skip to content

Commit 1eb2d22

Browse files
authored
Merge pull request #122 from OpenTrons/302-m-ignore-config-get-commands-on-linux-temporary-fix-for-ntnu
302 m ignore config get commands on linux temporary fix for ntnu
2 parents b0bac07 + ec29c5e commit 1eb2d22

File tree

10 files changed

+208
-138
lines changed

10 files changed

+208
-138
lines changed

MANIFEST.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
include versioneer.py
22
include opentrons/_version.py
3-
include opentrons/config/containers/default-containers.json
3+
include opentrons/config/containers/default-containers.json
4+
include opentrons/config/smoothie/smoothie-defaults.ini

conda.recipe/meta.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ test:
3030
imports:
3131
- opentrons
3232
- opentrons.config
33+
- opentrons.config.smoothie
3334
- opentrons.config.containers
3435
- opentrons.containers
3536
- opentrons.drivers
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[serial]
2+
baudrate = 115200
3+
timeout = 0.1
4+
5+
[state]
6+
head_speed = 3000
7+
8+
[config]
9+
version = v1.2.0
10+
ot_version = one_pro
11+
alpha_steps_per_mm = 80.0
12+
beta_steps_per_mm = 80.0
13+
gamma_steps_per_mm = 1068.7
14+
15+
[versions]
16+
firmware = ["v1.0.5"]
17+
config = ["v1.2.0"]
18+
ot_versions = {
19+
"hood": [400, 250, 100],
20+
"one_standard": [400, 400, 100],
21+
"one_pro": [400, 400, 100]
22+
}
23+

opentrons/drivers/motor.py

Lines changed: 129 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
1+
import configparser
12
import glob
23
import json
34
import math
5+
import os
6+
import pkg_resources
47
import sys
58
import time
69
from threading import Event
710

811
import serial
912

10-
from opentrons.drivers.virtual_smoothie import VirtualSmoothie
1113
from opentrons.util.log import get_logger
1214
from opentrons.util.vector import Vector
1315

1416
from opentrons.util import trace
1517

1618

19+
DEFAULTS_DIR_PATH = pkg_resources.resource_filename(
20+
'opentrons.config', 'smoothie')
21+
DEFAULTS_FILE_PATH = os.path.join(DEFAULTS_DIR_PATH, 'smoothie-defaults.ini')
22+
CONFIG_DIR_PATH = os.environ.get('APP_DATA_DIR', os.getcwd())
23+
CONFIG_DIR_PATH = os.path.join(CONFIG_DIR_PATH, 'smoothie')
24+
CONFIG_FILE_PATH = os.path.join(CONFIG_DIR_PATH, 'smoothie-config.ini')
25+
1726
JSON_ERROR = None
1827
if sys.version_info > (3, 4):
1928
JSON_ERROR = ValueError
@@ -41,23 +50,22 @@ class CNCDriver(object):
4150
ACCELERATION = 'M204'
4251
MOTORS_ON = 'M17'
4352
MOTORS_OFF = 'M18'
53+
STEPS_PER_MM = 'M92'
4454

4555
DISENGAGE_FEEDBACK = 'M63'
4656

4757
ABSOLUTE_POSITIONING = 'G90'
4858
RELATIVE_POSITIONING = 'G91'
4959

50-
GET_OT_VERSION = 'config-get sd ot_version'
60+
CONFIG_GET = 'config-get sd'
61+
CONFIG_SET = 'config-set sd'
62+
OT_VERSION = 'ot_version'
5163
GET_FIRMWARE_VERSION = 'version'
52-
GET_CONFIG_VERSION = 'config-get sd version'
53-
GET_STEPS_PER_MM = {
54-
'x': 'config-get sd alpha_steps_per_mm',
55-
'y': 'config-get sd beta_steps_per_mm'
56-
}
57-
58-
SET_STEPS_PER_MM = {
59-
'x': 'config-set sd alpha_steps_per_mm ',
60-
'y': 'config-set sd beta_steps_per_mm '
64+
CONFIG_VERSION = 'version'
65+
CONFIG_STEPS_PER_MM = {
66+
'x': 'alpha_steps_per_mm',
67+
'y': 'beta_steps_per_mm',
68+
'z': 'gamma_steps_per_mm'
6169
}
6270

6371
MOSFET = [
@@ -74,32 +82,68 @@ class CNCDriver(object):
7482
"""
7583
connection = None
7684

77-
serial_timeout = 0.1
85+
serial_timeout = None
86+
serial_baudrate = None
7887

79-
# TODO: move to config
80-
COMPATIBLE_FIRMARE = ['v1.0.5']
81-
COMPATIBLE_CONFIG = ['v1.2.0']
8288
firmware_version = None
8389
config_version = None
84-
8590
ot_version = None
86-
ot_one_dimensions = {
87-
'hood': Vector(400, 250, 100),
88-
'one_pro': Vector(400, 400, 100),
89-
'one_standard': Vector(400, 400, 100)
90-
}
9191

9292
def __init__(self):
93+
9394
self.stopped = Event()
9495
self.can_move = Event()
9596
self.resume()
96-
self.head_speed = 3000 # smoothie's default speed in mm/minute
9797
self.current_commands = []
9898

99-
self.SMOOTHIE_SUCCESS = 'Succes'
99+
self.SMOOTHIE_SUCCESS = 'Success'
100100
self.SMOOTHIE_ERROR = 'Received unexpected response from Smoothie'
101101
self.STOPPED = 'Received a STOP signal and exited from movements'
102102

103+
self.ignore_smoothie_sd = False
104+
105+
self.defaults = configparser.ConfigParser()
106+
self.defaults.read(DEFAULTS_FILE_PATH)
107+
self._create_saved_settings_file()
108+
self.saved_settings = configparser.ConfigParser()
109+
self.saved_settings.read(CONFIG_FILE_PATH)
110+
self._copy_defaults_to_settings()
111+
self._apply_settings()
112+
113+
def _create_saved_settings_file(self):
114+
if not os.path.isdir(CONFIG_DIR_PATH):
115+
os.mkdir(CONFIG_DIR_PATH)
116+
if not os.path.isfile(CONFIG_FILE_PATH):
117+
with open(CONFIG_FILE_PATH, 'w') as configfile:
118+
configfile.write('')
119+
120+
def _copy_defaults_to_settings(self):
121+
for n in self.defaults.sections():
122+
if n not in self.saved_settings:
123+
self.saved_settings[n] = self.defaults[n]
124+
for key, val in self.defaults[n].items():
125+
if key not in self.saved_settings[n]:
126+
self.saved_settings[n][key] = val
127+
128+
def _apply_settings(self):
129+
self.serial_timeout = float(
130+
self.saved_settings['serial'].get('timeout', 0.1))
131+
self.serial_baudrate = int(
132+
self.saved_settings['serial'].get('baudrate', 115200))
133+
134+
self.head_speed = int(
135+
self.saved_settings['state'].get('head_speed', 3000))
136+
137+
self.COMPATIBLE_FIRMARE = json.loads(
138+
self.saved_settings['versions'].get('firmware', '[]'))
139+
self.COMPATIBLE_CONFIG = json.loads(
140+
self.saved_settings['versions'].get('config', '[]'))
141+
self.ot_one_dimensions = json.loads(
142+
self.saved_settings['versions'].get('ot_versions', '{}'))
143+
for key in self.ot_one_dimensions.keys():
144+
axis_size = Vector(self.ot_one_dimensions[key])
145+
self.ot_one_dimensions[key] = axis_size
146+
103147
def get_connected_port(self):
104148
"""
105149
Returns the port the driver is currently connected to
@@ -128,6 +172,8 @@ def get_serial_ports_list(self):
128172
sys.platform.startswith('cygwin')):
129173
# this excludes your current terminal "/dev/tty"
130174
ports = glob.glob('/dev/tty*')
175+
# ignore Smoothie's local storage if linux (temporary work-around)
176+
self.ignore_smoothie_sd = True
131177
elif sys.platform.startswith('darwin'):
132178
ports = glob.glob('/dev/tty.*')
133179
else:
@@ -157,6 +203,12 @@ def connect(self, device):
157203
self.reset_port()
158204
log.debug("Connected to {}".format(device))
159205
self.versions_compatible()
206+
# set the previously saved steps_per_mm values for X and Y
207+
if self.ignore_smoothie_sd:
208+
for axis in 'xyz':
209+
self.set_steps_per_mm(
210+
axis, self.saved_settings['config'].get(
211+
self.CONFIG_STEPS_PER_MM[axis]))
160212
return self.calm_down()
161213

162214
def is_connected(self):
@@ -184,7 +236,7 @@ def check_paused_stopped(self):
184236
self.can_move.wait()
185237
if self.stopped.is_set():
186238
self.resume()
187-
raise RuntimeWarning('Stop signal received')
239+
raise RuntimeWarning(self.STOPPED)
188240

189241
def send_command(self, command, **kwargs):
190242
"""
@@ -194,7 +246,7 @@ def send_command(self, command, **kwargs):
194246
Returns a string with the Smoothie board's response
195247
Empty string if no response from Smoothie
196248
197-
>>> send_command(self.MOVE, x=100 y=100)
249+
send_command(self.MOVE, x=100 y=100)
198250
G0 X100 Y100
199251
"""
200252

@@ -243,14 +295,8 @@ def wait_for_response(self, timeout=20.0):
243295
raise RuntimeWarning('no response after {} seconds'.format(timeout))
244296

245297
def flush_port(self):
246-
# if we are running a virtual smoothie
247-
# we don't need a timeout for flush
248-
if isinstance(self.connection, VirtualSmoothie):
249-
self.readline_from_serial()
250-
else:
298+
while self.readline_from_serial():
251299
time.sleep(self.serial_timeout)
252-
while self.readline_from_serial():
253-
time.sleep(self.serial_timeout)
254300

255301
def readline_from_serial(self):
256302
msg = b''
@@ -477,6 +523,9 @@ def calibrate_steps_per_mm(self, axis, expected_travel, actual_travel):
477523
def set_head_speed(self, rate=None):
478524
if rate:
479525
self.head_speed = rate
526+
self.saved_settings['state']['head_speed'] = str(self.head_speed)
527+
with open(CONFIG_FILE_PATH, 'w') as configfile:
528+
self.saved_settings.write(configfile)
480529
kwargs = {"F": self.head_speed}
481530
res = self.send_command(self.SET_SPEED, **kwargs)
482531
return res == b'ok'
@@ -499,7 +548,7 @@ def versions_compatible(self):
499548
}
500549
if self.firmware_version not in self.COMPATIBLE_FIRMARE:
501550
res['firmware'] = False
502-
if self.config_version not in self.COMPATIBLE_CONFIG:
551+
if self.config_file_version not in self.COMPATIBLE_CONFIG:
503552
res['config'] = False
504553
if not self.ot_version:
505554
res['ot_version'] = False
@@ -511,15 +560,14 @@ def versions_compatible(self):
511560
'config={config}, '
512561
'ot_version={ot_version}'.format(
513562
firmware=self.firmware_version,
514-
config=self.config_version,
563+
config=self.config_file_version,
515564
ot_version=self.ot_version
516565
)
517566
)
518567
return res
519568

520569
def get_ot_version(self):
521-
res = self.send_command(self.GET_OT_VERSION)
522-
res = res.decode().split(' ')[-1]
570+
res = self.get_config_value(self.OT_VERSION)
523571
self.ot_version = None
524572
if res not in self.ot_one_dimensions:
525573
log.debug('{} is not an ot_version'.format(res))
@@ -538,24 +586,57 @@ def get_firmware_version(self):
538586
return self.firmware_version
539587

540588
def get_config_version(self):
541-
res = self.send_command(self.GET_CONFIG_VERSION)
542-
res = res.decode().split(' ')[-1]
543-
self.config_version = res
544-
return self.config_version
589+
res = self.get_config_value(self.CONFIG_VERSION)
590+
self.config_file_version = res
591+
return self.config_file_version
545592

546593
def get_steps_per_mm(self, axis):
547-
if axis not in self.GET_STEPS_PER_MM:
594+
if axis.lower() not in 'xyz':
548595
raise ValueError('Axis {} not supported'.format(axis))
549-
res = self.send_command(self.GET_STEPS_PER_MM[axis])
550-
return float(res.decode().split(' ')[-1])
596+
597+
res = self.send_command(self.STEPS_PER_MM)
598+
self.wait_for_response() # extra b'ok' sent from smoothie after M92
599+
try:
600+
value = json.loads(res.decode())[self.STEPS_PER_MM][axis.upper()]
601+
return float(value)
602+
except Exception:
603+
raise RuntimeError(
604+
'{0}: {1}'.format(self.SMOOTHIE_ERROR, res))
551605

552606
def set_steps_per_mm(self, axis, value):
553-
if axis not in self.SET_STEPS_PER_MM:
607+
if axis.lower() not in 'xyz':
554608
raise ValueError('Axis {} not supported'.format(axis))
555-
command = self.SET_STEPS_PER_MM[axis]
556-
command += str(value)
557-
res = self.send_command(command)
558-
return res.decode().split(' ')[-1] == str(value)
609+
610+
res = self.send_command(self.STEPS_PER_MM, **{axis.upper(): value})
611+
self.wait_for_response() # extra b'ok' sent from smoothie after M92
612+
613+
key = self.CONFIG_STEPS_PER_MM[axis.lower()]
614+
try:
615+
response_dict = json.loads(res.decode())
616+
returned_value = response_dict[self.STEPS_PER_MM][axis.upper()]
617+
self.set_config_value(key, str(returned_value))
618+
return float(returned_value) == value
619+
except Exception:
620+
raise RuntimeError(
621+
'{0}: {1}'.format(self.SMOOTHIE_ERROR, res))
622+
623+
def get_config_value(self, key):
624+
res = self.saved_settings['config'].get(key)
625+
if not self.ignore_smoothie_sd:
626+
command = '{0} {1}'.format(self.CONFIG_GET, key)
627+
res = self.send_command(command).decode().split(' ')[-1]
628+
return res
629+
630+
def set_config_value(self, key, value):
631+
success = True
632+
if not self.ignore_smoothie_sd:
633+
command = '{0} {1} {2}'.format(self.CONFIG_SET, key, value)
634+
res = self.send_command(command)
635+
success = res.decode().split(' ')[-1] == str(value)
636+
self.saved_settings['config'][key] = value
637+
with open(CONFIG_FILE_PATH, 'w') as configfile:
638+
self.saved_settings.write(configfile)
639+
return success
559640

560641
def get_endstop_switches(self):
561642
first_line = self.send_command(self.GET_ENDSTOPS)

opentrons/drivers/virtual_smoothie.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@ def __init__(self, port, options):
3939
'min_a': 0,
4040
'min_b': 0
4141
}
42+
self.steps_per_mm = {
43+
'X': self.config.get('alpha_steps_per_mm', 80),
44+
'Y': self.config.get('beta_steps_per_mm', 80),
45+
'Z': self.config.get('gamma_steps_per_mm', 1068.7),
46+
'F': 60
47+
}
4248
self.init_coordinates()
4349

4450
def isOpen(self):
@@ -172,6 +178,14 @@ def process_config_set(self, arguments):
172178
return '{0}: {1} has been set to {2}'.format(
173179
folder, setting, value)
174180

181+
def process_steps_per_mm(self, arguments):
182+
for axis in arguments.keys():
183+
if axis.upper() in 'XYZ':
184+
self.steps_per_mm[axis.upper()] = arguments[axis]
185+
response = json.dumps({'M92': self.steps_per_mm})
186+
response += '\nok'
187+
return response
188+
175189
def process_dwell_command(self, arguments):
176190
return 'ok'
177191

@@ -206,6 +220,7 @@ def process_command(self, command):
206220
'G92': self.process_set_position_command,
207221
'G28': self.process_home_command,
208222
'M119': self.process_get_endstops,
223+
'M92': self.process_steps_per_mm,
209224
'M999': self.process_calm_down,
210225
'M63': self.process_disengage_feedback,
211226
'G90': self.process_absolute_positioning,

opentrons/instruments/instrument.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ def create_command(self, do, setup=None, description=None, enqueue=True):
8686
>>> print(' world')
8787
>>> description = 'printing "hello world"'
8888
>>> instrument.create_command(do, setup, description)
89+
hello
8990
>>> robot.simulate()
9091
hello world
9192
>>> instrument.create_command(do, setup, description, enqueue=False)

0 commit comments

Comments
 (0)