rintcore 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3a9b723838919efffb3f912ecefcbbbea54b9ab8
4
+ data.tar.gz: ce1a9e323a978ae0e7452c722aadb13f259e436b
5
+ SHA512:
6
+ metadata.gz: 9fa795c99cd6f8783942d2a1fe206f5c0b4994acd0447b3e67f05984a89e6eed2e7e8e31633b0bd5be667e3634b09859086b09c8225e5dbaccf0024a821b3576
7
+ data.tar.gz: dfd942aa41baaf1fe91ffaff69aa3731cee4c8ae88dd697d5eae5bb4dd32339bb274ecdb438879e7d121080081ed2d9da6d929d4dc3491c03ded7b0f6f98c6ec
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.0.0-p0
data/CONTRIBUTING.md CHANGED
@@ -16,15 +16,19 @@ you can share.
16
16
 
17
17
  If you'd like to submit a pull request please adhere to the following:
18
18
 
19
- 1. Your code *must* be tested. Please TDD your code!
20
- 2. No single-character variables
21
- 3. Two-spaces instead of tabs
22
- 4. Single-quotes instead of double-quotes unless you are using string
19
+ 1. Your code *must* be tested. Please TDD your code! (where ever possible)
20
+ 2. do modifications on an appropriately named branch.
21
+ 3. No single-character variables
22
+ 4. Two-spaces instead of tabs
23
+ 5. Single-quotes instead of double-quotes unless you are using string
23
24
  interpolation or escapes.
24
- 5. General Rails/Ruby naming conventions for files and classes
25
+ 6. General Rails/Ruby naming conventions for files and classes
26
+ 7. Have your commit message prefixed with the class it impacts Eg.: [PrinterDriver] Made it more awesome.
27
+ 8. A single commit can only impact one class. If your changes impact multiple classes, then
28
+ your pull request must have multiple commits.
25
29
 
26
30
  Plase note that you must adhere to each of the above mentioned rules.
27
31
  Failure to do so will result in an immediate closing of the pull
28
32
  request. If you update and rebase the pull request to follow the
29
33
  guidelines your pull request will be re-opened and considered for
30
- inclusion.
34
+ inclusion.
data/README.md CHANGED
@@ -1,10 +1,30 @@
1
- # Hornet
1
+ # RintCore
2
+
3
+ [![Code Climate](https://codeclimate.com/github/KazW/RintCore.png)](https://codeclimate.com/github/KazW/RintCore)
2
4
 
3
5
  A RepRap/GCode parsing and sending utility written in Ruby.
4
6
 
5
- ### TODO
7
+ ### Usage
8
+ Install: ```gem install rintcore```
9
+
10
+ Get stats for a GCode file: ```rintcore analyze my_print.gcode```
11
+ Print a GCode file: ```rintcore print my_print.gcode```
12
+ See more options: ```rintcore help```
13
+
14
+ Use it somewhere else:
15
+ ```ruby
16
+ require 'rint_core/printer'
17
+ require 'rint_core/g_code/object'
18
+ gcode = RintCore::GCode::Object.new('my_print.gcode')
19
+ printer = RintCore::Printer.new
20
+ printer.port = '/dev/ttyUSB0' # Set to /dev/ttyACM0 by default
21
+ printer.baud = 250000 # Set to 115200 by default
22
+ printer.callbacks[:temperature] = Proc.new { |line| puts(line) }
23
+ printer.connect!
24
+ printer.start_print gcode```
6
25
 
7
- Update this list and everything else in general.
26
+ ### TODO
27
+ Documentation and spec writing.
8
28
 
9
29
  ### Contributing
10
30
  See CONTRIBUTING.md
@@ -13,7 +33,7 @@ See CONTRIBUTING.md
13
33
 
14
34
  Copyright (C) 2013 Kaz Walker
15
35
 
16
- The PrinterDriver class is based on printcore.py by Kliment Yanev and various contributors.
36
+ The Driver modules are based on printcore.py by Kliment Yanev and various contributors.
17
37
 
18
38
  This program is free software: you can redistribute it and/or modify
19
39
  it under the terms of the GNU General Public License as
data/bin/rintcore ADDED
@@ -0,0 +1,5 @@
1
+ #!env ruby
2
+ require 'rubygems'
3
+ require 'rint_core/cli'
4
+
5
+ RintCore::Cli.start
@@ -0,0 +1,87 @@
1
+ require 'rint_core/g_code/object'
2
+ require 'rint_core/printer'
3
+ require 'rint_core/g_code/codes'
4
+ require 'thor'
5
+ require 'active_support/core_ext/object/blank'
6
+
7
+ module RintCore
8
+ class Cli < Thor
9
+ map '-a' => :analyze, '-p' => :print
10
+
11
+ desc 'analyze FILE', 'Get statistics about the given GCode file.'
12
+ method_option :decimals, default: 2, aliases: '-d', type: :numeric, desc: 'The number of decimal places given for measurements.'
13
+ def analyze(file)
14
+ unless RintCore::GCode::Object.is_file?(file)
15
+ puts "Non-exsitant file: #{file}"
16
+ exit
17
+ end
18
+ @object = RintCore::GCode::Object.new(RintCore::GCode::Object.get_file(file))
19
+ decimals = options[:decimals]
20
+ decimals ||= 2
21
+ output = <<EOS
22
+ Dimensions:
23
+ \tX: #{@object.x_min.round(decimals)} - #{@object.x_max.round(decimals)} (#{@object.width.round(decimals)}mm)
24
+ \tY: #{@object.y_min.round(decimals)} - #{@object.y_max.round(decimals)} (#{@object.depth.round(decimals)}mm)
25
+ \tZ: #{@object.z_min.round(decimals)} - #{@object.z_max.round(decimals)} (#{@object.height.round(decimals)}mm)
26
+ Total Travel:
27
+ \tX: #{@object.x_travel.round(decimals)}mm
28
+ \tY: #{@object.y_travel.round(decimals)}mm
29
+ \tZ: #{@object.z_travel.round(decimals)}mm
30
+ Filament used: #{@object.filament_used.round(decimals)}mm
31
+ Number of layers: #{@object.layers}
32
+ #{@object.raw_data.length} lines / #{@object.lines.length} commands
33
+ EOS
34
+ puts output
35
+ end
36
+
37
+ desc 'print FILE', 'Print the given GCode file.'
38
+ method_option :port, aliases: '-p', type: :string, desc: 'The port that the printer is connected to.'
39
+ method_option :baud, aliases: '-b', type: :numeric, desc: 'The baud rate at which the printer communicates at.'
40
+ method_option :loud, aliases: '-l', default: false, type: :boolean, desc: 'Output additional info (temperature, progress, etc.)'
41
+ def print(file)
42
+ analyze(file)
43
+ port = options[:port]
44
+ baud = options[:baud]
45
+ baud = baud.to_i unless baud.blank?
46
+ baud = nil unless RintCore::Printer.baud_rates.include?(baud)
47
+ port = nil unless RintCore::Printer.is_port?(port)
48
+ while port.blank?
49
+ puts "Please enter the port and press enter:"
50
+ port = $stdin.gets.strip
51
+ port = nil unless RintCore::Printer.is_port?(port)
52
+ end
53
+ while baud.blank?
54
+ puts "Please enter the baud rate and press enter:"
55
+ baud = $stdin.gets.strip
56
+ baud = baud.to_i unless baud.blank?
57
+ baud = nil unless RintCore::Printer.baud_rates.include?(baud)
58
+ end
59
+ printer = RintCore::Printer.new
60
+ printer.port = port
61
+ printer.baud = baud
62
+ printer.callbacks[:online] = Proc.new { puts "Printer online!" }
63
+ printer.callbacks[:start] = Proc.new { puts "Started printing!" }
64
+ printer.callbacks[:finish] = Proc.new { puts "Print took: "+printer.time_from_start }
65
+ printer.callbacks[:temperature] = Proc.new { |line| puts line }
66
+ if options[:loud]
67
+ printer.callbacks[:receive] = Proc.new { |line| puts "Got: "+line }
68
+ printer.callbacks[:send] = Proc.new { |line| puts "Sent: "+line }
69
+ end
70
+ printer.callbacks[:disconnect] = Proc.new { puts "Printer disconnected!" }
71
+ printer.connect!
72
+ until printer.online?
73
+ sleep(printer.long_sleep)
74
+ end
75
+ start_time = Time.now
76
+ printer.start_print(@object)
77
+ while printer.printing?
78
+ printer.send_now(RintCore::GCode::Codes::GET_EXT_TEMP)
79
+ sleep 4.20
80
+ puts "Printed "+((Float(printer.queue_index) / Float(printer.main_queue.length))*100).round(2).to_s+"% in "+printer.time_from_start
81
+ sleep 1
82
+ end
83
+ printer.disconnect!
84
+ end
85
+
86
+ end
87
+ end
@@ -0,0 +1,22 @@
1
+ require 'rint_core/driver/operating_system'
2
+ require 'rint_core/driver/state'
3
+ require 'rint_core/driver/parsing'
4
+ require 'rint_core/driver/queueing'
5
+ require 'rint_core/driver/operations'
6
+
7
+ module RintCore
8
+ module Driver
9
+ include RintCore::Driver::OperatingSystem
10
+ include RintCore::Driver::State
11
+ include RintCore::Driver::Parsing
12
+ include RintCore::Driver::Queueing
13
+ include RintCore::Driver::Operations
14
+
15
+ def initialize_driver
16
+ initialize_state
17
+ initialize_queueing
18
+ initialize_operations
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,30 @@
1
+ module RintCore
2
+ module Driver
3
+ module OperatingSystem
4
+ def control_ttyhup(port, disable_hup)
5
+ if get_os == :linux
6
+ if disable_hup
7
+ `stty -F #{port} -hup`
8
+ else
9
+ `stty -F #{port} hup`
10
+ end
11
+ end
12
+ end
13
+
14
+ def enable_hup(port)
15
+ control_ttyhup(port, true)
16
+ end
17
+
18
+ def disable_hup(port)
19
+ control_ttyhup(port, false)
20
+ end
21
+
22
+ def get_os
23
+ return :linux if /linux/ =~ RUBY_PLATFORM
24
+ return :mac if /darwin/ =~ RUBY_PLATFORM
25
+ return :windows if /cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
26
+ return :unknown
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,209 @@
1
+ require 'rint_core/g_code/codes'
2
+ require 'rint_core/g_code/object'
3
+ require 'serialport'
4
+ require 'active_support/core_ext/object/blank'
5
+
6
+ module RintCore
7
+ module Driver
8
+ module Operations
9
+
10
+ def connect!
11
+ return false if connected?
12
+ if config.port.present? && config.baud.present?
13
+ disable_hup(config.port)
14
+ @connection = SerialPort.new(config.port, config.baud)
15
+ @connection.read_timeout = config.read_timeout
16
+ @stop_listening = false
17
+ sleep(config.long_sleep)
18
+ @listening_thread = Thread.new{listen()}
19
+ config.callbacks[:connect].call if config.callbacks[:connect].present?
20
+ end
21
+ end
22
+
23
+ def disconnect!
24
+ if connected?
25
+ if @listening_thread
26
+ @stop_listening = true
27
+ send!(RintCore::GCode::Codes::GET_EXT_TEMP)
28
+ @listening_thread.join
29
+ @listening_thread = nil
30
+ end
31
+ @connection.close
32
+ end
33
+ @connection = nil
34
+ offline!
35
+ not_printing!
36
+ config.callbacks[:disconnect].call if config.callbacks[:disconnect].present?
37
+ end
38
+
39
+ def reset!
40
+ @connection.dtr = 0
41
+ sleep(config.long_sleep)
42
+ @connection.dtr = 1
43
+ end
44
+
45
+ def pause!
46
+ return false unless printing?
47
+ @paused = true
48
+ not_printing!
49
+ @print_thread.join
50
+ @print_thread = nil
51
+ config.callbacks[:pause].call if config.callbacks[:pause].present?
52
+ end
53
+
54
+ def resume!
55
+ return false unless paused?
56
+ paused!
57
+ printing!
58
+ @print_thread = Thread.new{print!()}
59
+ config.callbacks[:resume].call if config.callbacks[:resume].present?
60
+ end
61
+
62
+ def send(command, wait = 0, priority = false)
63
+ if online?
64
+ if printing?
65
+ priority ? @priority_queue.push(command) : @main_queue.push(command)
66
+ else
67
+ until clear_to_send? do
68
+ sleep(config.sleep_time)
69
+ end
70
+ wait = config.wait_period if wait == 0 && config.wait_period > 0
71
+ not_clear_to_send!
72
+ send!(command)
73
+ while wait > 0 && !clear_to_send? do
74
+ sleep config.sleep_time
75
+ wait -= 1
76
+ end
77
+ end
78
+ else
79
+ # TODO: log something about not being connected to printer
80
+ end
81
+ end
82
+
83
+ def send_now(command, wait = 0)
84
+ send(command, wait, true)
85
+ end
86
+
87
+ def start_print(data, start_index = 0)
88
+ return false unless can_print?
89
+ data = data.lines if data.class == RintCore::GCode::Object
90
+ printing!
91
+ @main_queue = [] + data
92
+ @line_number = 0
93
+ @queue_index = start_index
94
+ @resend_from = -1
95
+ send!(RintCore::GCode::Codes::SET_LINE_NUM, -1, true)
96
+ return true unless data.present?
97
+ @print_thread = Thread.new{print!()}
98
+ @start_time = Time.now
99
+ return true
100
+ end
101
+
102
+ private
103
+
104
+ def initialize_operations
105
+ @connection = nil
106
+ @listening_thread = nil
107
+ @printing_thread = nil
108
+ @full_history = []
109
+ end
110
+
111
+ def readline!
112
+ begin
113
+ line = @connection.readline.strip
114
+ rescue EOFError, Errno::ENODEV => e
115
+ config.callbacks[:critcal_error].call(e) if config.callbacks[:critcal_error].present?
116
+ end
117
+ end
118
+
119
+ def print!
120
+ @machine_history = []
121
+ config.callbacks[:start].call if config.callbacks[:start].present?
122
+ while online? && printing? do
123
+ advance_queue
124
+ end
125
+ @print_thread.join
126
+ @print_thread = nil
127
+ config.callbacks[:finish].call if config.callbacks[:finish].present?
128
+ return true
129
+ end
130
+
131
+ def listen
132
+ clear_to_send!
133
+ listen_until_online
134
+ while listen_can_continue? do
135
+ line = readline!
136
+ @last_line_received = line
137
+ case get_response_type(line)
138
+ when :valid
139
+ config.callbacks[:receive].call(line) if config.callbacks[:receive].present?
140
+ clear_to_send!
141
+ when :online
142
+ config.callbacks[:receive].call(line) if config.callbacks[:receive].present?
143
+ when :temperature
144
+ config.callbacks[:temperature].call(line) if config.callbacks[:temperature].present?
145
+ when :temperature_response
146
+ config.callbacks[:temperature].call(line) if config.callbacks[:temperature].present?
147
+ clear_to_send!
148
+ when :error
149
+ config.callbacks[:printer_error] if config.callbacks[:printer_error].present?
150
+ # TODO: Figure out if an error should be raised here or if it should be left to the callback
151
+ when :resend
152
+ @resend_from = get_resend_number(line)
153
+ config.callbacks[:resend] if config.callbacks[:resend].present?
154
+ clear_to_send!
155
+ when :debug
156
+ config.callbacks[:debug] if config.callbacks[:debug].present?
157
+ when :invalid
158
+ config.callbacks[:invalid_response] if config.callbacks[:invalid_response].present?
159
+ #break
160
+ end
161
+ # clear_to_send!
162
+ end
163
+ #clear_to_send!
164
+ end
165
+
166
+ def listen_until_online
167
+ begin
168
+ empty_lines = 0
169
+ accepted_reponses = [:online,:temperature,:valid]
170
+ while listen_can_continue? do
171
+ line = readline!
172
+ if line.present?
173
+ empty_lines = 0
174
+ else
175
+ empty_lines += 1
176
+ not_clear_to_send!
177
+ send!(RintCore::GCode::Codes::GET_EXT_TEMP)
178
+ end
179
+ break if empty_lines == 5
180
+ if accepted_reponses.include?(get_response_type(line))
181
+ config.callbacks[:online].call if config.callbacks[:online].present?
182
+ online!
183
+ return true
184
+ end
185
+ sleep(config.long_sleep)
186
+ end
187
+ raise "Printer could not be brought online."
188
+ rescue RuntimeError => e
189
+ config.callbacks[:critcal_error].call(e) if config.callbacks[:critcal_error].present?
190
+ end
191
+ end
192
+
193
+ def send!(command, line_number = 0, calc_checksum = false)
194
+ if calc_checksum
195
+ command = prefix_command(command, line_number)
196
+ @machine_history[line_number] = command unless command.include?(RintCore::GCode::Codes::SET_LINE_NUM)
197
+ end
198
+ if connected?
199
+ config.callbacks[:send].call(command) if online? && config.callbacks[:send].present?
200
+ command = format_command(command)
201
+ @connection.write(command)
202
+ @full_history << command
203
+ end
204
+ end
205
+
206
+
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,39 @@
1
+ require 'rint_core/g_code/codes'
2
+ require 'active_support/core_ext/object/blank'
3
+
4
+ module RintCore
5
+ module Driver
6
+ module Parsing
7
+
8
+ def format_command(command)
9
+ (command.strip + "\n").split(RintCore::GCode::Codes::COMMENT_SYMBOL).first.encode(config.encoding)
10
+ end
11
+
12
+ def get_checksum(command)
13
+ command.bytes.inject{|a,b| a^b}.to_s
14
+ end
15
+
16
+ def prefix_command(command, line_number)
17
+ prefix = ('N' + line_number.to_s + ' ' + command.strip).encode(config.encoding)
18
+ prefix+'*'+get_checksum(prefix)
19
+ end
20
+
21
+ def get_response_type(line)
22
+ return :invalid unless line.present? || line.class == String
23
+ return :error if line.include?(config.error_response)
24
+ return :debug if line.include?(config.debug_response)
25
+ return :online if line.start_with?(*config.online_response)
26
+ return :valid if line.start_with?(*config.good_response) && !line.include?(config.temperature_response)
27
+ return :temperature_response if line.start_with?(*config.good_response) && line.include?(config.temperature_response)
28
+ return :temperature if line.include?(config.temperature_response)
29
+ return :resend if line.start_with?(*config.resend_response)
30
+ return :invalid
31
+ end
32
+
33
+ def get_resend_number(line)
34
+ line.sub('N:', '').sub('N', '').sub(':', '').strip.split.first.to_i
35
+ end
36
+
37
+ end
38
+ end
39
+ end