rublicatorg 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+