-
Notifications
You must be signed in to change notification settings - Fork 24
/
LaserSpeed.py
253 lines (229 loc) · 10.1 KB
/
LaserSpeed.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
#!/usr/bin/env python
from math import floor, ceil
class LaserSpeed:
"""
MIT License.
This is the standard library for converting to and from speed code information for LHYMICRO-GL.
The units in the speed code have particular bands/gears which switches the equations used to convert
between values and speeds. The fundamental units within the speed code value is period. All values
are linearly related to the delay between ticks. The device controlled is ultimately a stepper motor
and the speed a stepper motor travels at is the result of the time between ticks. We are dealing with
a 1000 dpi stepper motor, so for example to travel at 1 inch a second requires that the device tick
at 1 kHz. To do this it must delay 1 ms between ticks. This corresponds to a value of 48296 in the M2
board. Which has an equation of 60416 - (12120 * T) where T is the period requested in ms. This is
equal to 25.4 mm/s. If we want a 2 ms delay, which is half the speed (0.5kHz, 0.5 inches/second,
12.7 mm/s) we do 60416 - (12120 * 2) which gives us a value of 36176. This would be encoded a 16 bit
number broken up into 2 ascii 3 digit strings between 0-255. 141 for the high bits and 80 for the low
bits. So CV1410801 where the final 1 is the gearing equation we used.
The speed in mm/s is also used for determining which gearing to use and as a factor for the horizontal
encoded value. Slow down the device down while traveling diagonal to make the diagonal and orthogonal
take the same amount of time (thereby cutting to the same depth).
"""
def __init__(self):
pass
@staticmethod
def get_speed_from_code(speed_code, board="M2"):
code_value, gear, step_value, diagonal, raster_step = LaserSpeed.parse_speed_code(speed_code)
# b, m, gear = LaserSpeed.get_gearing(board, code_value, raster_step == 0)
b, m, gear = LaserSpeed.get_gearing(board, gear=gear, uses_raster_step=raster_step != 0)
return LaserSpeed.get_speed_from_value(code_value, b, m)
@staticmethod
def get_code_from_speed(mm_per_second, raster_step=0, board="M2", d_ratio=0.261199033289, gear=None):
"""
Get a speedcode from a given speed. The raster step appends the 'G' value and uses speed ranges.
The d_ratio uses the default/auto ratio, but might be improved at sqrt(2)-1 (0.41421356).
The gearing is optional and forces the speedcode to work for that particular gearing. Gear=0
refers to C-suffix notation speeds.
:param mm_per_second: speed to convert to code.
:param raster_step: raster step mode to use.
:param board: Nano Board Model to do the conversion for.
:param d_ratio: M1, M2, B1, B2 have ratio of optional speed
:param gear: Optional force gearing rather than default gear for that speed.
:return: speed code produced.
"""
if mm_per_second > 240 and raster_step == 0:
mm_per_second = 19.05 # Arbitrary default speed for out range value.
b, m, gear = LaserSpeed.get_gearing(board, mm_per_second, raster_step != 0, gear)
speed_value = LaserSpeed.get_value_from_speed(mm_per_second, b, m)
if (speed_value - round(speed_value)) > 0.005:
speed_value = ceil(speed_value)
speed_value = round(speed_value)
encoded_speed = LaserSpeed.encode_value(speed_value)
if raster_step != 0:
if gear == 0: # There is no C suffix notation for gear raster step.
gear = 1
return "V%s%1dG%03d" % (
encoded_speed,
gear,
raster_step
)
if d_ratio == 0 or board == "A" or board == "B" or board == "M":
# We do not need the diagonal code.
if raster_step == 0:
if gear == 0:
return "CV%s1C" % (
encoded_speed
)
else:
return "CV%s%1d" % (
encoded_speed,
gear)
else:
step_value = min(int(floor(mm_per_second) + 1), 128)
frequency_kHz = float(mm_per_second) / 25.4
try:
period_in_ms = 1 / frequency_kHz
except ZeroDivisionError:
period_in_ms = 0
d_value = d_ratio * -m * period_in_ms / float(step_value)
encoded_diagonal = LaserSpeed.encode_value(d_value)
if gear == 0:
return "CV%s1%03d%sC" % (
encoded_speed,
step_value,
encoded_diagonal
)
else:
return "CV%s%1d%03d%s" % (
encoded_speed,
gear,
step_value,
encoded_diagonal)
@staticmethod
def parse_speed_code(speed_code):
is_shortened = False
normal = False
if speed_code[0] == "C":
speed_code = speed_code[1:]
normal = True
if speed_code[-1] == "C":
speed_code = speed_code[:-1]
is_shortened = True
# This is an error speed.
if "V1677" in speed_code or "V1676" in speed_code:
# The 4th character can only be 0,1,2 except for error speeds.
code_value = LaserSpeed.decode_value(speed_code[1:12])
speed_code = speed_code[12:]
# The value for this speed is so low, it's negative
# and bit-shifted in 24 bits of a negative number.
else:
code_value = LaserSpeed.decode_value(speed_code[1:7])
speed_code = speed_code[7:]
gear = int(speed_code[0])
speed_code = speed_code[1:]
if is_shortened:
gear = 0 # Flags as step zero during code error.
raster_step = 0
if normal:
step_value = 0
diagonal = 0
if len(speed_code) > 1:
step_value = int(speed_code[:3])
diagonal = LaserSpeed.decode_value(speed_code[3:])
return code_value, gear, step_value, diagonal, raster_step
else:
if "G" in speed_code:
raster_step = int(speed_code[-3:])
return code_value, gear, 1, 1, raster_step
@staticmethod
def get_value_from_speed(mm_per_second, b, m):
"""
Takes in speed in mm per second and returns speed value.
"""
try:
frequency_kHz = float(mm_per_second) / 25.4
period_in_ms = 1 / frequency_kHz
return LaserSpeed.get_value_from_period(period_in_ms, b, m)
except ZeroDivisionError:
return b
@staticmethod
def get_value_from_period(x, b, m):
"""
Takes in period in ms and converts it to value.
This is a simple linear relationship.
"""
return m * x + b
@staticmethod
def get_speed_from_value(value, b, m):
try:
period_in_ms = LaserSpeed.get_period_from_value(value, b, m)
frequency_kHz = 1 / period_in_ms
return 25.4 * frequency_kHz
except ZeroDivisionError:
return 0
@staticmethod
def get_period_from_value(y, b, m):
try:
return (y - b) / m
except ZeroDivisionError:
return float('inf')
@staticmethod
def decode_value(code):
b1 = int(code[0:-3])
if b1 > 16000000:
b1 -= 16777216 # decode error negative numbers
b2 = int(code[-3:])
return (b1 << 8) + b2
@staticmethod
def encode_value(value):
value = int(value)
b0 = value & 255
b1 = (value >> 8) & 0xFFFFFF # unsigned shift, to emulate bugged form.
return "%03d%03d" % (b1, b0)
@staticmethod
def get_gear_for_speed(mm_per_second, uses_raster_step=False):
if mm_per_second <= 25.4:
return 1
if 25.4 < mm_per_second <= 60:
return 2
if not uses_raster_step:
if 60 < mm_per_second < 127:
return 3
if 127 <= mm_per_second:
return 4
else:
if 60 < mm_per_second < 127:
return 2
if 127 <= mm_per_second <= 320:
return 3
if 320 <= mm_per_second:
return 4
@staticmethod
def get_gearing(board, mm_per_second=None, uses_raster_step=False, gear=None):
if gear is None:
gear = LaserSpeed.get_gear_for_speed(mm_per_second, uses_raster_step)
# A, B, B1, B2
b_values = [64752.0, 64752.0, 64640.0, 64512.0]
m = -2000.0
if board[0] == "M": # any M series board
b_values = [60416.0, 60416.0, 59904.0, 59392.0]
m = -12120.0
if board == "B2":
m = -24240.0
if gear == 0:
if board == "B2":
if uses_raster_step:
return b_values[0], m / 12, 1
else:
return b_values[0], m / 12, 0
elif board == "M" or board == "M1":
return b_values[0], m, 0
elif board == "M2":
return 65528.0, m / 12, 0
elif mm_per_second is not None:
if board == "B2":
if mm_per_second < 7:
if uses_raster_step:
return b_values[0], m / 12, 1
else:
return b_values[0], m / 12, 0
elif board == "M":
if mm_per_second < 6:
return b_values[0], m, 0
elif board == "M1":
if mm_per_second < 6 or (not uses_raster_step and mm_per_second < 7):
return b_values[0], m, 0
elif board == "M2":
if mm_per_second < 7:
return 65528.0, m / 12, 0
return b_values[gear - 1], m, gear