rublicatorg 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc ADDED
@@ -0,0 +1,20 @@
1
+ Provides a programming interface to commnicate with a MakerBot 3D Printer.
2
+
3
+ Should eventually be able to do everything that ReplicatorG can do... but for now it is very incomplete.
4
+
5
+ =Ruby gem usage
6
+
7
+ require "rublicatorg"
8
+
9
+ makerbot = RublicatorG.new("/dev/tty.usbserial-FTE3Q0I3")
10
+
11
+ puts "Motherboard Firmware Version: #{makerbot.motherboard_version}"
12
+
13
+ =Command line
14
+
15
+ Also installs a rublicatorg binary that can be called on the command line.
16
+
17
+ Usage: rublicatorg [options] [file]
18
+ -l, --list List the files stored on the SD card
19
+ -r, --run FILE Run the FILE from the SD card
20
+ -h, --help Display this screen
data/bin/rublicatorg ADDED
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "rublicatorg"
4
+ require "optparse"
5
+ require "parseconfig"
6
+
7
+ options = {}
8
+
9
+ optparse = OptionParser.new do |opts|
10
+ opts.banner = "Usage: rublicatorg [options] [file]"
11
+
12
+ options[:port] = false
13
+ opts.on('-p', '--port PORT', 'Serial port (can also be specified in ~/.rublicatorgrc)') do |p|
14
+ options[:port] = p
15
+ end
16
+
17
+ options[:list] = false
18
+ opts.on('-l', '--list', 'List the files stored on the SD card') do |l|
19
+ options[:list] = l
20
+ end
21
+
22
+ options[:run] = false
23
+ opts.on('-r', '--run FILE', 'Run the FILE from the SD card') do |r|
24
+ options[:run] = r
25
+ end
26
+
27
+ opts.on_tail('-h', '--help', 'Display this screen') do
28
+ puts opts
29
+ exit
30
+ end
31
+ end
32
+
33
+ optparse.parse!
34
+
35
+ begin
36
+ if options[:port] == false
37
+ rcfile = File.expand_path("~/.rublicatorgrc")
38
+ if File.exists?(rcfile)
39
+ config = ParseConfig.new(rcfile)
40
+ options[:port] = config.get_value('port')
41
+ else
42
+ raise "Serial port not specified and not found in #{rcfile}"
43
+ end
44
+ end
45
+
46
+ makerbot = RublicatorG.new(options[:port])
47
+
48
+ case true
49
+ when options[:list]
50
+ filenames = makerbot.filenames.collect{|f| f.split(".")[0]}.join(",")
51
+ puts filenames
52
+ when options[:run] != false
53
+ makerbot.run(options[:run])
54
+ else
55
+ puts optparse
56
+ end
57
+ rescue Exception => e
58
+ puts "ERROR: #{e.message}"
59
+ exit(-1)
60
+ end
data/examples/name.rb ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "rublicatorg"
4
+
5
+ $DEBUG = true
6
+
7
+ makerbot = RublicatorG.new("/dev/tty.usbserial-A600emXZ")
8
+
9
+ old_name = makerbot.name
10
+ puts "Old Machine Name: #{old_name}"
11
+ makerbot.name = "Foo Bar"
12
+ puts "New Machine Name: #{makerbot.name}"
13
+ makerbot.name = old_name
14
+ puts "Reset Old Machine Name: #{makerbot.name}"
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "rublicatorg"
4
+
5
+ $DEBUG = true
6
+
7
+ makerbot = RublicatorG.new("/dev/tty.usbserial-A600emXZ")
8
+
9
+ makerbot.run("home.s3g")
10
+
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "rublicatorg"
4
+
5
+ $DEBUG = true
6
+
7
+ makerbot = RublicatorG.new("/dev/tty.usbserial-A600emXZ")
8
+
9
+ filenames = makerbot.filenames
10
+
11
+ puts filenames.inspect
12
+
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ require "rublicatorg"
4
+
5
+ $DEBUG = true
6
+
7
+ makerbot = RublicatorG.new("/dev/tty.usbserial-A600emXZ")
8
+
9
+ puts "Motherboard Firmware Version: #{makerbot.motherboard_version} (#{makerbot.motherboard_build_name})"
10
+ puts "Toolhead 0 Firmware Version: #{makerbot.toolhead_version(0)} (#{makerbot.toolhead_build_name(0)})"
11
+ makerbot.motherboard_init
12
+ puts "Machine Name: #{makerbot.name}"
@@ -0,0 +1,404 @@
1
+ #!/usr/bin/env ruby -wKU
2
+
3
+ # RublicatorG - Ruby Makerbot/RepRap Control
4
+ # Copyright (C) 2011 Tony Buser <tbuser@gmail.com> - http://tonybuser.com
5
+ #
6
+ # This program is free software; you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation; either version 2 of the License
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program; if not, write to the Free Software Foundation,
17
+ # Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18
+
19
+ # Protocol Definitions:
20
+ # https://docs.google.com/Doc?docid=0AcWKwJ2SAxDzZGd6amZyY2NfMmdtODRnZ2Ri&hl=en&pli=1
21
+ # http://replicat.org/sanguino3g#responsecode
22
+
23
+ require "rubygems"
24
+ require "serialport"
25
+
26
+ class Array
27
+ def hex_to_str
28
+ str = ""
29
+ self.collect{|b| str << "%c" % b}
30
+ str
31
+ end
32
+
33
+ def to_hex_str
34
+ self.collect{|e| "0x%02x" % e}.join(" ")
35
+ end
36
+ end
37
+
38
+ class String
39
+ def to_hex_str
40
+ str = ""
41
+ self.each_byte {|b| str << '0x%02x ' % b}
42
+ str.strip
43
+ end
44
+
45
+ def to_hex_array
46
+ arr = []
47
+ self.each_byte {|b| arr << '0x%02x' % b}
48
+ arr
49
+ end
50
+
51
+ def from_hex_str
52
+ data = self.split(' ')
53
+ str = ""
54
+ data.each{|h| eval "str += '%c' % #{h}"}
55
+ str
56
+ end
57
+ end
58
+
59
+ # class Bignum
60
+ # # This is needed because String#unpack() can't handle little-endian signed longs...
61
+ # # instead we unpack() as a little-endian unsigned long (i.e. 'V') and then use this
62
+ # # method to convert to signed long.
63
+ # def as_signed
64
+ # -1*(self^0xffffffff) if self > 0xfffffff
65
+ # end
66
+ # end
67
+
68
+ $DEV ||= nil
69
+
70
+ class RublicatorG
71
+ VERSION = "0.0.1"
72
+
73
+ START_BYTE = 0xd5
74
+
75
+ @@motherboard_codes = {
76
+ # query
77
+ "version" => 0x00,
78
+ "init" => 0x01,
79
+ "get_buffer_size" => 0x02,
80
+ "clear_buffer" => 0x03,
81
+ "get_position" => 0x04,
82
+ "get_range" => 0x05,
83
+ "set_range" => 0x06,
84
+ "abort" => 0x07,
85
+ "pause" => 0x08,
86
+ "probe" => 0x09,
87
+ "tool_query" => 0x0a,
88
+ "is_finished" => 0x0b,
89
+ "read_eeprom" => 0x0c,
90
+ "write_eeprom" => 0x0d,
91
+ "capture_to_file" => 0x0e,
92
+ "end_capture" => 0x0f,
93
+ "playback_file" => 0x10,
94
+ "reset" => 0x11,
95
+ "next_filename" => 0x12,
96
+ "read_debug_registers" => 0x13,
97
+ "get_build_name" => 0x14,
98
+ # buffered
99
+ "queue_point_absolute" => 0x81,
100
+ "set_position_registers" => 0x82,
101
+ "find_axes_min" => 0x83,
102
+ "find_axes_max" => 0x84,
103
+ "delay" => 0x85,
104
+ "change_tool" => 0x86,
105
+ "wait_for_tool" => 0x87,
106
+ "tool_command" => 0x88,
107
+ "enable_axes" => 0x89
108
+ }
109
+
110
+ @@toolhead_codes = {
111
+ "version" => 0x00,
112
+ "init" => 0x01,
113
+ "get_temperature" => 0x02,
114
+ "set_motor1_pwm" => 0x04,
115
+ "set_motor2_pwm" => 0x05,
116
+ "set_motor1_rpm" => 0x06,
117
+ "set_motor2_rpm" => 0x07,
118
+ "set_motor1_direction" => 0x08,
119
+ "set_motor2_direction" => 0x09,
120
+ "toggle_motor1" => 0x0a,
121
+ "toggle_motor2" => 0x0b,
122
+ "toggle_fan" => 0x0c,
123
+ "toggle_valve" => 0x0d,
124
+ "set_servo1_position" => 0x0e,
125
+ "set_servo2_position" => 0x0f,
126
+ "filament_status" => 0x10,
127
+ "get_motor1_rpm" => 0x11,
128
+ "get_motor2_rpm" => 0x12,
129
+ "get_motor1_pwm" => 0x13,
130
+ "get_motor2_pwm" => 0x14,
131
+ "select_tool" => 0x15,
132
+ "is_tool_ready" => 0x16,
133
+
134
+ "get_build_name" => 0x22
135
+ }
136
+
137
+ @@response_codes = {
138
+ 0x00 => "Generic error, packet discarded.",
139
+ 0x01 => "Success.",
140
+ 0x02 => "Action buffer overflow, entire packet discarded.",
141
+ 0x03 => "CRC mismatch, packet discarded.",
142
+ 0x04 => "Query packet too big, packet discarded.",
143
+ 0x05 => "Command not supported/recognized.",
144
+ 0x06 => "Success; expect more packets. Used when a single reponse packet cannot contain the entire message to be retrieved.",
145
+ 0x07 => "Downstream timeout (for example, a toolhead timed out)."
146
+ }
147
+
148
+ @@sd_response_codes = {
149
+ 0x00 => "operation was successful",
150
+ 0x01 => "no SD card was present",
151
+ 0x02 => "SD card init failed",
152
+ 0x03 => "partition table could not be read",
153
+ 0x04 => "filesystem could not be opened",
154
+ 0x05 => "root directory could not be opened",
155
+ 0x06 => "SD card is locked",
156
+ 0x07 => "no such file"
157
+ }
158
+
159
+ @@mutex = Mutex.new
160
+
161
+ def initialize(dev = nil)
162
+ dev ||= $DEV
163
+
164
+ @@mutex.synchronize do
165
+ begin
166
+ @sp = SerialPort.new(dev, 115200, 8, 1, SerialPort::NONE)
167
+
168
+ # @sp.flow_control = SerialPort::HARD
169
+ @sp.read_timeout = 5000
170
+ $stderr.puts "Cannot connect to #{dev}" if @sp.nil?
171
+ # rescue Errno::EBUSY
172
+ rescue
173
+ raise "Cannot connect. The serial port device is busy or unavailable."
174
+ end
175
+ end
176
+
177
+ puts "Connected to: serial port #{dev}" if $DEBUG
178
+ end
179
+
180
+ # Close the connection
181
+ def close
182
+ @@mutex.synchronize do
183
+ @sp.close if @sp and not @sp.closed?
184
+ end
185
+ end
186
+
187
+ # Returns true if the connection to the machine is open; false otherwise
188
+ def connected?
189
+ not @sp.closed?
190
+ end
191
+
192
+ def send_and_receive(payload, request_reply=true)
193
+ msg = [START_BYTE] + [payload.size] + payload + [crc8(payload.hex_to_str)]
194
+
195
+ send_cmd(msg)
196
+
197
+ # FIXME: ugly hackish timing, there are much more intelligent ways to handle the delay
198
+ sleep(1)
199
+
200
+ if request_reply
201
+ ok,response = recv_reply
202
+
203
+ if ok #and response[1] == op[1]
204
+ # data = response[3..response.size]
205
+ data = response
206
+ # TODO ? if data contains a \n character, ruby seems to pass the parts before and after the \n
207
+ # as two different parameters... we need to encode the data into a format that doesn't
208
+ # contain any \n's and then decode it in the receiving method
209
+ # data = data.to_hex_str
210
+ elsif !ok
211
+ $stderr.puts response
212
+ data = false
213
+ else
214
+ $stderr.puts "ERROR: Unexpected response #{response}"
215
+ data = false
216
+ end
217
+ else
218
+ data = true
219
+ end
220
+ data
221
+ end
222
+
223
+ def send_cmd(payload)
224
+ @@mutex.synchronize do
225
+ puts "Sending message: #{payload.to_hex_str}" if $DEBUG
226
+ payload.each do |b|
227
+ @sp.putc b
228
+ end
229
+ end
230
+ end
231
+
232
+ def recv_reply
233
+ @@mutex.synchronize do
234
+ begin
235
+ header = @sp.sysread(2)
236
+ start_byte = header[0..0].to_hex_str
237
+ length = header[1..1].unpack("C")[0]
238
+
239
+ payload = @sp.sysread(length)
240
+ response_code = payload[0..0].to_hex_str
241
+ msg = payload[1..-1]
242
+
243
+ crc = @sp.sysread(1).to_hex_str
244
+
245
+ # rescue EOFError
246
+ rescue
247
+ raise "Cannot read from the machine. Make sure the device is on and connected."
248
+ end
249
+
250
+ puts "Received Message: start_byte: #{start_byte} length: #{length} response_code: #{response_code} payload: #{msg.to_hex_str} crc: #{crc}" if $DEBUG
251
+
252
+ if response_code != '0x81'
253
+ error = "ERROR: #{@@response_codes[response_code]}"
254
+ return [false,error]
255
+ end
256
+
257
+ return [true,msg]
258
+ end
259
+ end
260
+
261
+ def crc8(str, crc=0)
262
+ str.each_byte do |byte|
263
+ # * This is a Java implementation of the IButton/Maxim 8-bit CRC. Code ported
264
+ # * from the AVR-libc implementation, which is used on the RR3G end.
265
+ # crc = (crc ^ data) & 0xff; // i loathe java's promotion rules
266
+ # for (int i = 0; i < 8; i++) {
267
+ # if ((crc & 0x01) != 0) {
268
+ # crc = ((crc >>> 1) ^ 0x8c) & 0xff;
269
+ # } else {
270
+ # crc = (crc >>> 1) & 0xff;
271
+ # }
272
+ # }
273
+
274
+ crc = (crc ^ byte) & 0xff
275
+
276
+ 8.times do |i|
277
+ unless crc & 0x01 == 0
278
+ crc = ((crc >> 1) ^ 0x8c) & 0xff
279
+ else
280
+ crc = (crc >> 1) & 0xff
281
+ end
282
+ end
283
+ end
284
+
285
+ crc
286
+ end
287
+
288
+ def motherboard_version
289
+ if result = send_and_receive([@@motherboard_codes["version"]])
290
+ result = result.unpack("v")[0]
291
+ "#{result/100}.#{result % 100}"
292
+ else
293
+ false
294
+ end
295
+ end
296
+
297
+ def motherboard_build_name
298
+ if result = send_and_receive([@@motherboard_codes["get_build_name"]])
299
+ result
300
+ else
301
+ false
302
+ end
303
+ end
304
+
305
+ def motherboard_init
306
+ if result = send_and_receive([@@motherboard_codes["init"]])
307
+ result
308
+ else
309
+ false
310
+ end
311
+ end
312
+
313
+ def name
314
+ payload = []
315
+ payload << @@motherboard_codes["read_eeprom"]
316
+ # at position: 32, integer uint16 to 2 bytes
317
+ payload << (32 & 0xff)
318
+ payload << ((32 >> 8) & 0xff)
319
+ # length to read: 16
320
+ payload << 16
321
+
322
+ if result = send_and_receive(payload)
323
+ result
324
+ else
325
+ false
326
+ end
327
+ end
328
+
329
+ def name=(str)
330
+ raise "Name too long. Name must be <= 16 characters." if str.size > 16
331
+
332
+ payload = []
333
+ payload << @@motherboard_codes["write_eeprom"]
334
+ # at position: 32, integer uint16 to 2 bytes
335
+ payload << (32 & 0xff)
336
+ payload << ((32 >> 8) & 0xff)
337
+ # length to write
338
+ payload << 16
339
+ # send the name
340
+ str.ljust(16).each_byte do |byte|
341
+ payload << byte
342
+ end
343
+
344
+ if result = send_and_receive(payload)
345
+ result
346
+ else
347
+ false
348
+ end
349
+ end
350
+
351
+ def toolhead_version(tool_id=0)
352
+ payload = [@@motherboard_codes["tool_query"], tool_id, @@toolhead_codes["version"]]
353
+
354
+ if result = send_and_receive(payload)
355
+ result = result.unpack("v")[0]
356
+ "#{result/100}.#{result % 100}"
357
+ else
358
+ false
359
+ end
360
+ end
361
+
362
+ def toolhead_build_name(tool_id=0)
363
+ payload = [@@motherboard_codes["tool_query"], tool_id, @@toolhead_codes["get_build_name"]]
364
+
365
+ if result = send_and_receive(payload)
366
+ result
367
+ else
368
+ false
369
+ end
370
+ end
371
+
372
+ def filenames
373
+ filenames = []
374
+
375
+ # keep reading until result is blank? or a max of 100
376
+ 100.times do |x|
377
+ filename = send_and_receive([@@motherboard_codes["next_filename"], x == 0 ? 1 : 0]).gsub(/\000/, '')
378
+ if filename == ""
379
+ break
380
+ else
381
+ filenames << filename
382
+ end
383
+ end
384
+
385
+ filenames
386
+ end
387
+
388
+ def run(filename)
389
+ raise "Filename too long. Name must be <= 12 characters." if filename.size > 12
390
+
391
+ payload = []
392
+ payload << @@motherboard_codes["playback_file"]
393
+ filename.each_byte do |byte|
394
+ payload << byte
395
+ end
396
+
397
+ if result = send_and_receive(payload)
398
+ result
399
+ else
400
+ false
401
+ end
402
+ end
403
+
404
+ end
@@ -0,0 +1,9 @@
1
+ require 'test/unit'
2
+ require 'rublicatorg'
3
+
4
+ class RublicatorgTest < Test::Unit::TestCase
5
+ def test_derp
6
+ # uhm, I need like a mock makerbot to do automated tests?
7
+ assert_equal 0xd5, RublicatorG::START_BYTE
8
+ end
9
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rublicatorg
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - Tony Buser
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-11-10 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: serialport
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: parseconfig
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :runtime
43
+ version_requirements: *id002
44
+ description: Communicate with a MakerBot 3D Printer
45
+ email: tbuser@gmail.com
46
+ executables:
47
+ - rublicatorg
48
+ extensions: []
49
+
50
+ extra_rdoc_files: []
51
+
52
+ files:
53
+ - lib/rublicatorg.rb
54
+ - bin/rublicatorg
55
+ - examples/name.rb
56
+ - examples/play_sd_card.rb
57
+ - examples/read_sd_card.rb
58
+ - examples/version.rb
59
+ - test/test_rublicatorg.rb
60
+ - README.rdoc
61
+ has_rdoc: true
62
+ homepage: http://github.com/tbuser/RublicatorG
63
+ licenses: []
64
+
65
+ post_install_message:
66
+ rdoc_options: []
67
+
68
+ require_paths:
69
+ - lib
70
+ required_ruby_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ segments:
82
+ - 0
83
+ version: "0"
84
+ requirements: []
85
+
86
+ rubyforge_project: rublicatorg
87
+ rubygems_version: 1.3.6
88
+ signing_key:
89
+ specification_version: 3
90
+ summary: ReplicatorG in Ruby!
91
+ test_files: []
92
+