3D Print Server: (WIP) Stream GCODE to Marlin 3D Printer with Ruby on macOS

Here is version 0.11 of my Ruby code to send GCODE to a 3D printer using Marlin firmware.

Edit: Current version is 0.17 after tweaking when posting a 3D Benchy sliced with Cura and adding SIGINT actions graceful shutdown actions

Seems to print OK; but of course needs to be tested in many different failure modes.

Note: Have not yet tested with filament in the printer. So far, have only tested watching the movement of the hot-end with no filament.

##############################################
# marlin_printer.rb
# Author:  Neo
# URL:  https://community.unix.com
# Summary:  Send GCODE to a Marlin 3D printer
# Version: 0.11
# Date: 7 November 2021
##############################################

require 'serialport'


def print_gcode
    
    if Rails.application.config.marlin_device.present?
        device = Rails.application.config.marlin_device
    else
        get_device
        if Rails.application.config.marlin_device.present?
            device = Rails.application.config.marlin_device
        else
            puts "[#{Time.now}] Connection to device failed!"
            return false
        end
    end
    sp = SerialPort.new  device
    baud_rate = 115200
    gcode_file = "./Ender3_Level_Test_v2.gcode"
    ##########################################################
    # these are the supported modem baud rates according to Ruby
    # but the ender 3v2 only seems to support rates up to 115200
    # 2400 (24)
    # 9600 (96)
    # 19200 (19, 192)
    # 38400 (38, 384) 
    # 57600 (57, 576)
    # 115200 (115, 1152)
    # 250000 (250)  <- Not supported on Ender 3v2
    # 500000 (500)  <- Not supported on Ender 3v2
    # 1000000       <- Not supported on Ender 3v2
    ##########################################################
    sp.baud=baud_rate
    count = 0
    puts "[#{Time.now}] Connected to serial port #{device}, baud rate set to: #{sp.baud}"
    puts "[#{Time.now}] Read GCODE from file #{gcode_file}."
    gcode = []
    data = File.readlines(gcode_file)
    if data.count < 1
        puts "[#{Time.now}] FAILURE: Read GCODE from file #{gcode_file} failed!"
        return false
    end

    
    data.each do |line|
        gcode << line if line[0] != ';'
    end
    
    if gcode.count < 1
        puts "[#{Time.now}] FAILURE: GCODE file empty after stripping comments!"
        return false
    end
    
    gcode_lines = gcode.count
    word = ''
    puts "[#{Time.now}] GCODE File Size to Print: #{gcode_lines} Lines"
    sp.write gcode[0]
    puts "[#{Time.now}] GCODE Sent #{gcode[count].chomp} OK: #{device}"
    count += 1
    # insure we reach the end of the code file
    # fix this later to me more accurate
  # marlin firmware seems not to return "ok" for some commands 
  # or my "ok" detection needs tweaking.
    remaining_lines = (gcode_lines *1.05).to_i
    remaining_lines.times do |n|
        begin
            test_line = sp.readline("\n")
             puts "[#{Time.now}] PRINTER Line #{count} Sent #{test_line.chomp} from GCODE #{gcode[count].chomp}"
            if test_line.downcase.chomp == 'ok'
                percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
                puts "[#{Time.now}] GCODE Sent Line #{count} #{gcode[count].chomp}"
                count += 1
                sp.write gcode[count] 
                puts "[#{Time.now}] GCODE Sent Line #{count}  #{gcode[count].chomp} -  File Progress: #{percent_file_remaining}%" 
            end
            rescue
                puts "[#{Time.now}] GCODE EXCEPTION #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}%"
                if count >= gcode_lines
                    remaining_lines = count
                    break
                end
                #exception occurs then nothing to read and read times out
                next
            end
        end
        percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
        puts "[#{Time.now}] GCODE Completed #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}%"
end
Sample Beginning Output:
2.7.1 :002 > print_gcode
[2021-11-07 12:18:50 +0700] Connected to serial port /dev/cu.wchusbserial140, baud rate set to: 115200
[2021-11-07 12:18:50 +0700] Read GCODE from file ./Ender3_Level_Test_v2.gcode.
[2021-11-07 12:18:50 +0700] GCODE File Size to Print: 6013 Lines
[2021-11-07 12:18:50 +0700] GCODE Sent M190 S70 OK: /dev/cu.wchusbserial140
[2021-11-07 12:18:50 +0700] PRINTER Line 1 Sent ok from GCODE M104 S220
[2021-11-07 12:18:50 +0700] GCODE Sent Line 1 M104 S220
[2021-11-07 12:18:50 +0700] GCODE Sent Line 2  M109 S200 -  File Progress: 0.02%
[2021-11-07 12:18:50 +0700] PRINTER Line 2 Sent //action:notification Bed Heating... from GCODE M109 S200
[2021-11-07 12:18:50 +0700] PRINTER Line 2 Sent  T:200.05 /200.00 B:69.99 /70.00 @:75 B@:35 W:? from GCODE M109 S200
[2021-11-07 12:18:51 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:69.97 /70.00 @:76 B@:46 W:12 from GCODE M109 S200
[2021-11-07 12:18:52 +0700] PRINTER Line 2 Sent echo:busy: processing from GCODE M109 S200
[2021-11-07 12:18:52 +0700] PRINTER Line 2 Sent  T:200.07 /200.00 B:69.96 /70.00 @:75 B@:48 W:11 from GCODE M109 S200
[2021-11-07 12:18:53 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:69.95 /70.00 @:77 B@:52 W:10 from GCODE M109 S200
[2021-11-07 12:18:54 +0700] PRINTER Line 2 Sent  T:199.99 /200.00 B:69.97 /70.00 @:78 B@:43 W:9 from GCODE M109 S200
[2021-11-07 12:18:54 +0700] PRINTER Line 2 Sent echo:busy: processing from GCODE M109 S200
[2021-11-07 12:18:55 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:69.96 /70.00 @:76 B@:52 W:8 from GCODE M109 S200
[2021-11-07 12:18:56 +0700] PRINTER Line 2 Sent  T:200.05 /200.00 B:69.99 /70.00 @:76 B@:38 W:7 from GCODE M109 S200
[2021-11-07 12:18:56 +0700] PRINTER Line 2 Sent echo:busy: processing from GCODE M109 S200
[2021-11-07 12:18:57 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:69.97 /70.00 @:76 B@:47 W:6 from GCODE M109 S200
[2021-11-07 12:18:58 +0700] PRINTER Line 2 Sent  T:200.05 /200.00 B:70.04 /70.00 @:76 B@:20 W:5 from GCODE M109 S200
[2021-11-07 12:18:58 +0700] PRINTER Line 2 Sent echo:busy: processing from GCODE M109 S200
[2021-11-07 12:18:59 +0700] PRINTER Line 2 Sent  T:200.05 /200.00 B:70.01 /70.00 @:76 B@:36 W:4 from GCODE M109 S200
[2021-11-07 12:19:00 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:70.01 /70.00 @:76 B@:38 W:3 from GCODE M109 S200
[2021-11-07 12:19:00 +0700] PRINTER Line 2 Sent echo:busy: processing from GCODE M109 S200
[2021-11-07 12:19:01 +0700] PRINTER Line 2 Sent  T:200.05 /200.00 B:70.01 /70.00 @:76 B@:36 W:2 from GCODE M109 S200
[2021-11-07 12:19:02 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:70.01 /70.00 @:76 B@:39 W:1 from GCODE M109 S200
[2021-11-07 12:19:02 +0700] PRINTER Line 2 Sent echo:busy: processing from GCODE M109 S200
[2021-11-07 12:19:03 +0700] PRINTER Line 2 Sent  T:200.03 /200.00 B:70.01 /70.00 @:76 B@:37 W:0 from GCODE M109 S200
...
Sample Ending Output:
2021-11-07 13:04:21 +0700] PRINTER Line 6007 Sent ok from GCODE M106 S0 ; turn off cooling fan
[2021-11-07 13:04:21 +0700] GCODE Sent Line 6007 M106 S0 ; turn off cooling fan
[2021-11-07 13:04:21 +0700] GCODE Sent Line 6008  M104 S0 ; turn off extruder -  File Progress: 95.15%
[2021-11-07 13:04:21 +0700] PRINTER Line 6008 Sent ok from GCODE M104 S0 ; turn off extruder
[2021-11-07 13:04:21 +0700] GCODE Sent Line 6008 M104 S0 ; turn off extruder
[2021-11-07 13:04:21 +0700] GCODE Sent Line 6009  M140 S0 ; turn off bed -  File Progress: 95.17%
[2021-11-07 13:04:21 +0700] PRINTER Line 6009 Sent //action:notification Bed Cooling... from GCODE M140 S0 ; turn off bed
[2021-11-07 13:04:21 +0700] PRINTER Line 6009 Sent //action:notification Ender-3 V2 Ready. from GCODE M140 S0 ; turn off bed
[2021-11-07 13:04:21 +0700] PRINTER Line 6009 Sent ok from GCODE M140 S0 ; turn off bed
[2021-11-07 13:04:21 +0700] GCODE Sent Line 6009 M140 S0 ; turn off bed
[2021-11-07 13:04:21 +0700] GCODE Sent Line 6010  M84 ; disable motors -  File Progress: 95.18%
[2021-11-07 13:04:23 +0700] PRINTER Line 6010 Sent echo:busy: processing from GCODE M84 ; disable motors
[2021-11-07 13:04:25 +0700] PRINTER Line 6010 Sent echo:busy: processing from GCODE M84 ; disable motors
[2021-11-07 13:04:27 +0700] PRINTER Line 6010 Sent ok from GCODE M84 ; disable motors
[2021-11-07 13:04:27 +0700] GCODE Sent Line 6010 M84 ; disable motors
[2021-11-07 13:04:27 +0700] GCODE Sent Line 6011  M82 ;absolute extrusion mode -  File Progress: 95.2%
[2021-11-07 13:04:27 +0700] PRINTER Line 6011 Sent ok from GCODE M82 ;absolute extrusion mode
[2021-11-07 13:04:27 +0700] GCODE Sent Line 6011 M82 ;absolute extrusion mode
[2021-11-07 13:04:27 +0700] GCODE Sent Line 6012  M104 S0 -  File Progress: 95.22%
[2021-11-07 13:04:27 +0700] PRINTER Line 6012 Sent //action:notification Ender-3 V2 Ready. from GCODE M104 S0
[2021-11-07 13:04:27 +0700] PRINTER Line 6012 Sent ok from GCODE M104 S0
[2021-11-07 13:04:27 +0700] GCODE Sent Line 6012 M104 S0
[2021-11-07 13:04:27 +0700] GCODE EXCEPTION 6013 Lines of 6013  -  File Progress: 95.23%
[2021-11-07 13:04:27 +0700] GCODE Completed 6013 Lines of 6013  -  File Progress: 100.0%
 => nil 

.....

See Also:

In version 0.12 I tweaked gcode delivery a bit (experimental):

##############################################
# marlin_printer.rb
# Author:  Neo
# URL:  https://community.unix.com
# Summary:  Send GCODE to a Marlin 3D printer
# Version: 0.12
# Date: 7 November 2021
##############################################

require 'serialport'


def print_gcode
    start_time = Time.now
    # gode delivery to printer is fast
    # slow gcode delivery by around 10 m 
    # later read this as an option from the Rail app
    slow_stream_down = 0.01 
    delay_before_final_close = 5.0
    slow_down_final_gcode = 0.1
    if Rails.application.config.marlin_device.present?
        device = Rails.application.config.marlin_device
    else
        get_device
        if Rails.application.config.marlin_device.present?
            device = Rails.application.config.marlin_device
        else
            puts "[#{Time.now}] Connection to device failed!"
            return false
        end
    end
    # additional gcode to insure motors and heaters are off
    # move hot end up and out of the way
    # later read this as an option from the Rail app
    add_ending_code = true
    ending_gcode = 
        [
            'G90',
            'G0 Y110 X200 Z60',
            'M106 S0',  
            'M106 S0 ',
            'M104 S0', 
            'M140 S0', 
            'M84', 
            'M82', 
            'M104 S0'
        ]
    sp = SerialPort.new  device
    # later read this as an option from the Rail app
    baud_rate = 115200
    # later read this as an option from the Rail app
    gcode_file = "./Ender3_Level_Test_v2.gcode"
    ##########################################################
    # these are the supported modem baud rates according to Ruby
    # but the ender 3v2 only seems to support rates up to 115200
    # 2400 (24)
    # 9600 (96)
    # 19200 (19, 192)
    # 38400 (38, 384) 
    # 57600 (57, 576)
    # 115200 (115, 1152)
    # 250000 (250)  <- Not supported on Ender 3v2
    # 500000 (500)  <- Not supported on Ender 3v2
    # 1000000       <- Not supported on Ender 3v2
    ##########################################################
    sp.baud=baud_rate
    count = 0
    puts "[#{Time.now}] Connected to serial port #{device}, baud rate set to: #{sp.baud}"
    puts "[#{Time.now}] Read GCODE from file #{gcode_file}."
    gcode = []
    gcode_raw = []
    data = File.readlines(gcode_file)
    if data.count < 1
        puts "[#{Time.now}] FAILURE: Read GCODE from file #{gcode_file} failed!"
        return false
    end

    
    data.each do |line|
        gcode_raw << line if line[0] != ';'
    end

    gcode_raw.each do |line|
        if false #line.include? ';'
            gcode << line.split(';')[0].chomp
        else
            gcode << line.gsub(/;.*/, '')
        end
    end
    
    if gcode.count < 1
        puts "[#{Time.now}] FAILURE: GCODE file empty after stripping comments!"
        return false
    end
    
    gcode_lines = gcode.count
    word = ''
    puts "[#{Time.now}] GCODE File Size to Print: #{gcode_lines} Lines"
    sp.write gcode[0]
    puts "[#{Time.now}] GCODE Sent #{gcode[count].chomp} OK: #{device}"
    count += 1
    # insure we reach the end of the code file
    # fix this later to me more accurate
    # marlin firmware seems not to return "ok" for some commands 
    # or my "ok" detection needs tweaking.
    remaining_lines = (gcode_lines *1.05).to_i
    remaining_lines.times do |n|
        sleep slow_stream_down #slow it down a little
        begin
            test_line = sp.readline("\n")
             puts "[#{Time.now}] PRINTER Line #{count} Sent #{test_line.chomp} from GCODE #{gcode[count].chomp}"
            if test_line.downcase.chomp == 'ok'
                
                percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
                puts "[#{Time.now}] GCODE Sent Line #{count} #{gcode[count].chomp}"
                count += 1
                sp.write gcode[count] 
                puts "[#{Time.now}] GCODE Sent Line #{count}  #{gcode[count].chomp} -  File Progress: #{percent_file_remaining}%" 
            end
            rescue
                puts "[#{Time.now}] GCODE EXCEPTION #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}%"
                if count >= gcode_lines
                    remaining_lines = count
                    add_ending_code = true
                    break
                end
                #exception occurs then nothing to read and read times out
                next
            end
        end
        
        if add_ending_code
            ending_gcode.each do |code|
                puts "[#{Time.now}] GCODE Ending Code (Extra): #{code}"
                sp.write code
                sleep slow_down_final_gcode  #slow down final gcode by 100ms
            end
        end
        total_time = ((Time.now - start_time)/60.0).to_f.round(2)
        percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
        puts "[#{Time.now}] GCODE Completed #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}% Time: #{total_time} mins"
        sleep delay_before_final_close
        sp.close
    end

It is "hard not to to be thrilled" at my first test print using my very own homebrew'ed, Ruby coded gcode server!

I have been researching 3D printing tech for over two months and this is the first time I have seen anyone write their own print server (except for well-known ones like OctoPrint). Also, for sure, this is the only Ruby 3D print server I have seen online or anywhere! Wow. Feels really good to code this from scratch, from only yesterday.

This first test print (a calibration print) was almost flawless. I feel like a 10 year old kid in a candy store after working two days and writing my first draft 3D print server to get this kind of first print results!

Ender3_Level_Test_v2.gcode (163.0 KB)

Now, I need to start building out a full blown Rails app to manage the print work-flow process and add more features!

Fun!

Note: This core print function is not yet perfect because when I tried a file which was orders of magnitude larger (a full-size 3DBenchy), it "hangs". So there are still a few small kinks to work out to insure it works on gcode models of all sizes and slicers.

Anyway, this is real progress...... :slight_smile:

4 Likes

OK. I figured out why Cura prints are hanging.

Cura inserts an (unnecessary) M105 (get temperature report) which is already reported by Merlin automatically.

This M105 response from Marlin (Jyers version) is formatted differently than other gcode responses.

Normally when a report or info is requested from the printer, it is returned to the server in the form:

text info
test info
ok

But this version of the firmware for only one gcode returns the M105 report:

ok report text, report text ... blah blah

this means we need a special handler for M105. I think better if the firmware makes the return format consistent; but anyway that is a different story.

In the meantime, I will just strip out the M105s:

data.each do |line|
        # strip out lines with are comments
        # strip out M105 commands added by Cura
        # maybe write a routine to deal with M105
        # since the problem is the fact that the "ok" and temperature
        # string is on the same line. should be the report a newline, then "ok"
        if line[0] != ';' && line.downcase.chomp != 'm105'
            gcode_raw << line
        end
    end

Now the Cura models are printing without hanging up and I'm in the process of printing the main calibration model, the 3DBenchy.

Currently printing this benchmark with my own Ruby 3D print server!

Let's see how this iconic benchmark model turns out in a few hours, currently progress is at only 9% of the file:

[2021-11-07 17:16:58 +0700] GCODE Sent Line 14830 G1 F2700 X119.202 Y123.388 E551.55344
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14831  G0 F9000 X119.227 Y123.097 -  File Progress: 9.4%
[2021-11-07 17:16:58 +0700] PRINTER Line 14831 Sent ok from GCODE G0 F9000 X119.227 Y123.097
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14831 G0 F9000 X119.227 Y123.097
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14832  G1 F2700 X101.197 Y127.928 E552.0501 -  File Progress: 9.4%
[2021-11-07 17:16:58 +0700] PRINTER Line 14832 Sent ok from GCODE G1 F2700 X101.197 Y127.928 E552.0501
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14832 G1 F2700 X101.197 Y127.928 E552.0501
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14833  G0 F9000 X101.406 Y130.32 -  File Progress: 9.4%
[2021-11-07 17:16:58 +0700] PRINTER Line 14833 Sent ok from GCODE G0 F9000 X101.406 Y130.32
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14833 G0 F9000 X101.406 Y130.32
[2021-11-07 17:16:58 +0700] GCODE Sent Line 14834  G1 F2700 X111.332 Y93.279 E553.07047 -  File Progress: 9.4%
[2021-11-07 17:16:59 +0700] PRINTER Line 14834 Sent ok from GCODE G1 F2700 X111.332 Y93.279 E553.07047
[2021-11-07 17:16:59 +0700] GCODE Sent Line 14834 G1 F2700 X111.332 Y93.279 E553.07047
[2021-11-07 17:16:59 +0700] GCODE Sent Line 14835  G0 F9000 X112.238 Y93.911 -  File Progress: 9.4%
[2021-11-07 17:16:59 +0700] PRINTER Line 14835 Sent ok from GCODE G0 F9000 X112.238 Y93.911
[2021-11-07 17:16:59 +0700] GCODE Sent Line 14835 G0 F9000 X112.238 Y93.911
[2021-11-07 17:16:59 +0700] GCODE Sent Line 14836  G1 F2700 X105.705 Y95.661 E553.25043 -  File Progress: 9.4%
[2021-11-07 17:16:59 +0700] PRINTER Line 14836 Sent ok from GCODE G1 F2700 X105.705 Y95.661 E553.25043
[2021-11-07 17:16:59 +0700] GCODE Sent Line 14836 G1 F2700 X105.705 Y95.661 E553.25043
[2021-11-07 17:16:59 +0700] GCODE Sent Line 14837  G0 F9000 X107.159 Y94.374 -  File Progress: 9.4%
[2021-11-07 17:17:00 +0700] PRINTER Line 14837 Sent ok from GCODE G0 F9000 X107.159 Y94.374
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14837 G0 F9000 X107.159 Y94.374
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14838  G1 F2700 X120.146 Y107.362 E553.73914 -  File Progress: 9.41%
[2021-11-07 17:17:00 +0700] PRINTER Line 14838 Sent ok from GCODE G1 F2700 X120.146 Y107.362 E553.73914
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14838 G1 F2700 X120.146 Y107.362 E553.73914
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14839  G0 F9000 X120.088 Y106.963 -  File Progress: 9.41%
[2021-11-07 17:17:00 +0700] PRINTER Line 14839 Sent ok from GCODE G0 F9000 X120.088 Y106.963
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14839 G0 F9000 X120.088 Y106.963
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14840  G1 F2700 X112.015 Y137.095 E554.56917 -  File Progress: 9.41%
[2021-11-07 17:17:00 +0700] PRINTER Line 14840 Sent ok from GCODE G1 F2700 X112.015 Y137.095 E554.56917
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14840 G1 F2700 X112.015 Y137.095 E554.56917
[2021-11-07 17:17:00 +0700] GCODE Sent Line 14841  G0 F9000 X113.352 Y137.094 -  File Progress: 9.41%
[2021-11-07 17:17:01 +0700] PRINTER Line 14841 Sent ok from GCODE G0 F9000 X113.352 Y137.094
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14841 G0 F9000 X113.352 Y137.094
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14842  G1 F2700 X118.106 Y135.82 E554.70013 -  File Progress: 9.41%
[2021-11-07 17:17:01 +0700] PRINTER Line 14842 Sent ok from GCODE G1 F2700 X118.106 Y135.82 E554.70013
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14842 G1 F2700 X118.106 Y135.82 E554.70013
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14843  G0 F9000 X120.318 Y110.381 -  File Progress: 9.41%
[2021-11-07 17:17:01 +0700] PRINTER Line 14843 Sent ok from GCODE G0 F9000 X120.318 Y110.381
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14843 G0 F9000 X120.318 Y110.381
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14844  G1 F2700 X100.12 Y115.793 E555.25652 -  File Progress: 9.41%
[2021-11-07 17:17:01 +0700] PRINTER Line 14844 Sent ok from GCODE G1 F2700 X100.12 Y115.793 E555.25652
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14844 G1 F2700 X100.12 Y115.793 E555.25652
[2021-11-07 17:17:01 +0700] GCODE Sent Line 14845  G0 F9000 X99.679 Y109.699 -  File Progress: 9.41%
[2021-11-07 17:17:02 +0700] PRINTER Line 14845 Sent ok from GCODE G0 F9000 X99.679 Y109.699
[2021-11-07 17:17:02 +0700] GCODE Sent Line 14845 G0 F9000 X99.679 Y109.699
[2021-11-07 17:17:02 +0700] GCODE Sent Line 14846  G1 F2700 X119.521 Y104.383 E555.8031 -  File Progress: 9.41%
[2021-11-07 17:17:02 +0700] PRINTER Line 14846 Sent ok from GCODE G1 F2700 X119.521 Y104.383 E555.8031
[2021-11-07 17:17:02 +0700] GCODE Sent Line 14846 G1 F2700 X119.521 Y104.383 E555.8031
[2021-11-07 17:17:02 +0700] GCODE Sent Line 14847  G1 F1500 E549.3031 -  File Progress: 9.41%
[2021-11-07 17:17:02 +0700] PRINTER Line 14847 Sent ok from GCODE G1 F1500 E549.3031

Sounds nutty; but it is really thrilling to print the most iconic 3D calibration model, the Benchy, with a print server I wrote myself in less than two days, from scratch, using Ruby (no code examples were found on the net, totally scratch)!!

1 Like

Success!! 3DBenchy printed with Ruby new print server code [version 0.15, tweaked code to deal with "special" (corner-case) condition].

marlin_printer.rb version 0.15

##############################################
# marlin_printer.rb
# Author:  Neo
# URL:  https://community.unix.com
# Summary:  Send GCODE to a Marlin 3D printer
# Version: 0.15
# Date: 7 November 2021
##############################################

require 'serialport'


def print_gcode
    debug_method = false
    start_time = Time.now
    # gode delivery to printer is fast
    # slow gcode delivery by around 10 ms 
    # later read this as an option from the Rails app
    slow_stream_down = 0.01 
    delay_before_final_close = 5.0
    slow_down_final_gcode = 0.1
    if Rails.application.config.marlin_device.present?
        device = Rails.application.config.marlin_device
    else
        get_device
        if Rails.application.config.marlin_device.present?
            device = Rails.application.config.marlin_device
        else
            puts "[#{Time.now}] Connection to device failed!"
            return false
        end
    end
    # additional gcode to insure motors and heaters are off
    # move hot end up and out of the way
    # later read this as an option from the Rail app
    add_ending_code = true
   # temporarily here.   move this to rails UI as an option later
    ending_gcode = 
        [
            'G90',
            'G0 Y110 X200 Z60',
            'M106 S0',  
            'M106 S0 ',
            'M104 S0', 
            'M140 S0', 
            'M84', 
            'M82', 
            'M104 S0'
        ]
    sp = SerialPort.new  device
    # later read this as an option from the Rail app
    baud_rate = 115200
    # later read this as an option from the Rail app
    #gcode_file = "./Ender3_Level_Test_v2.gcode"
    gcode_file = "./3DBenchy_50mm_F2.gcode"
    ##########################################################
    # these are the supported modem baud rates according to Ruby
    # but the ender 3v2 only seems to support rates up to 115200
    # 2400 (24)
    # 9600 (96)
    # 19200 (19, 192)
    # 38400 (38, 384) 
    # 57600 (57, 576)
    # 115200 (115, 1152)
    # 250000 (250)  <- Not supported on Ender 3v2
    # 500000 (500)  <- Not supported on Ender 3v2
    # 1000000       <- Not supported on Ender 3v2
    ##########################################################
    sp.baud=baud_rate
    count = 0
    puts "[#{Time.now}] Connected to serial port #{device}, baud rate set to: #{sp.baud}"
    puts "[#{Time.now}] Read GCODE from file #{gcode_file}."
    gcode = []
    gcode_raw = []
    data = File.readlines(gcode_file)
    if data.count < 1
        puts "[#{Time.now}] FAILURE: Read GCODE from file #{gcode_file} failed!"
        return false
    end

    
    data.each do |line|
        # strip out lines with are comments
        # strip out M105 commands added by Cura
        # maybe write a routine to deal with M105
        # since the problem is the fact that the "ok" and temperature
        # string is on the same line. should be the report a newline, then "ok"
        # see: https://github.com/Jyers/Marlin/discussions/1596
        if line[0] != ';' && line.downcase.chomp != 'm105' && line.chomp.length > 1
            # strip all comments after gcode
            gcode << line.gsub(/;.*/, '')
        else
            puts "[#{Time.now}] STRIPPING GCODE: #{line.chomp} "  if debug_method
        end
    end

    # if false # delete this
    #     gcode_raw.each do |line|
    #         if false #line.include? ';'
    #             gcode << line.split(';')[0].chomp
    #         else
    #             gcode << line.gsub(/;.*/, '')
    #         end
    #     end
    # end
    
    if gcode.count < 1
        puts "[#{Time.now}] FAILURE: GCODE file empty after stripping comments!"
        return false
    end
    
    gcode_lines = gcode.count
    word = ''
    puts "[#{Time.now}] GCODE File Size to Print: #{gcode_lines} Lines"
    sp.write gcode[0]
    puts "[#{Time.now}] GCODE Sent #{gcode[count].chomp} OK: #{device}"
    count += 1
    # insure we reach the end of the code file
    # fix this later to me more accurate
    # marlin firmware seems not to return "ok" for some commands 
    # or my "ok" detection needs tweaking.
    remaining_lines = (gcode_lines *1.05).to_i
    remaining_lines.times do |n|
        sleep slow_stream_down #slow it down a little
        begin
            test_line = sp.readline("\n")
             puts "[#{Time.now}] PRINTER Line #{count} Sent #{test_line.chomp} from GCODE #{gcode[count].chomp}"
             # test_line.downcase.chomp == 'okk' is a bandaid for a bug(?) from Marlin returning an 'okk' 
             # or other unknown bug returned in 3DBenchy test
             # [2021-11-07 22:36:30 +0700] GCODE Sent Line 116446  G0 X117.456 Y112.564 -  File Progress: 73.81%
             if test_line.downcase.chomp == 'ok' || test_line.downcase.chomp == 'okk'
                
                percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
                puts "[#{Time.now}] GCODE Sent Line #{count} #{gcode[count].chomp}"
                
                if gcode[count].chomp.length > 1
                    count += 1
                    sp.write gcode[count] if gcode[count]
                    puts "[#{Time.now}] GCODE Sent Line #{count}  #{gcode[count].chomp} -  File Progress: #{percent_file_remaining}%" 
                else
                    puts "[#{Time.now}] GCODE NOT Sent Line #{count}  #{gcode[count]} -  File Progress: #{percent_file_remaining}%" 
                end
             end
            rescue
                puts "[#{Time.now}] GCODE EXCEPTION #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}%"
                if count >= gcode_lines
                    remaining_lines = count
                    add_ending_code = true
                    break
                end
                #exception occurs then nothing to read and read times out
                next
            end
        end
        
        if add_ending_code
            ending_gcode.each do |code|
                puts "[#{Time.now}] GCODE Ending Code (Extra): #{code}"
                sp.write "#{code}\r"
                sleep slow_down_final_gcode  #slow down final gcode by 10ms
            end
        end
        total_time = ((Time.now - start_time)/60.0).to_f.round(2)
        percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
        puts "[#{Time.now}] GCODE Completed #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}% Time: #{total_time} mins"
        sleep delay_before_final_close
        sp.close
    end
2 Likes

Update:

  • Added signal trap for SIGINT for graceful shutdown of printer
  • Tweaked exception gcode (debugging)
#############################################
# marlin_printer.rb
# Author:  Neo
# URL:  https://community.unix.com
# Summary:  Send GCODE to a Marlin 3D printer
# Version: 0.17
# Date: 8 November 2021
##############################################

require 'serialport'


def print_gcode
    
    if Rails.application.config.marlin_device.present?
        device = Rails.application.config.marlin_device
    else
        get_device
        if Rails.application.config.marlin_device.present?
            device = Rails.application.config.marlin_device
        else
            puts "[#{Time.now}] Connection to device failed!"
            return false
        end
    end

    

    debug_method = false
    start_time = Time.now
    strip_m105 = true
     # later read this as an option from the Rail app
    baud_rate = 115200
    # gode delivery to printer is fast
    # slow gcode delivery by around 10 m 
    # later read this as an option from the Rail app
    slow_stream_down = 0.01 
    delay_before_final_close = 5.0
    slow_down_final_gcode = 0.1
    count = 0
    gcode = []

    # additional gcode to insure motors and heaters are off
    # move hot end up and out of the way
    # later read this as an option from the Rail app
    add_ending_code = true

    ending_gcode = 
        [
            'G90',
            'G0 Y110 X200 Z20',
            'M106 S0',  
            'M104 S0', 
            'M140 S0', 
        ]
    sp = SerialPort.new  device
    Signal.trap("INT") { 
        shutdown_gcode_on_sigint(sp, ending_gcode)
        exit
    }
    # later read this as an option from the Rail app
    #gcode_file = "./Ender3_Level_Test_v2.gcode"
    gcode_file = "./3DBenchy_50mm_F2.gcode"
    ##########################################################
    # these are the supported modem baud rates according to Ruby
    # but the ender 3v2 only seems to support rates up to 115200
    # 2400 (24)
    # 9600 (96)
    # 19200 (19, 192)
    # 38400 (38, 384) 
    # 57600 (57, 576)
    # 115200 (115, 1152)
    # 250000 (250)  <- Not supported on Ender 3v2
    # 500000 (500)  <- Not supported on Ender 3v2
    # 1000000       <- Not supported on Ender 3v2
    ##########################################################
    sp.baud=baud_rate
    
    puts "[#{Time.now}] Connected to serial port #{device}, baud rate set to: #{sp.baud}"
    puts "[#{Time.now}] Read GCODE from file #{gcode_file}."
    
    data = File.readlines(gcode_file)
    if data.count < 1
        puts "[#{Time.now}] FAILURE: Read GCODE from file #{gcode_file} failed!"
        return false
    end

    
    data.each do |line|
        # strip out lines with are comments
        # strip out M105 commands added by Cura if enabled
       
        if line[0] != ';'  && line.chomp.length > 1
            if line.downcase.chomp == 'm105' && strip_m105
                # strip all comments after gcode
                puts "[#{Time.now}] STRIPPING M105 GCODE: #{line.chomp} "  if debug_method
            else
                gcode << line.gsub(/;.*/, '')
            end
        else
            puts "[#{Time.now}] STRIPPING GCODE: #{line.chomp} "  if debug_method
        end
    end

    
    if gcode.count < 1
        puts "[#{Time.now}] FAILURE: GCODE file empty after stripping comments!"
        return false
    end
    
    gcode_lines = gcode.count
    word = ''
    puts "[#{Time.now}] GCODE File Size to Print: #{gcode_lines} Lines"
    sp.write gcode[0]
    puts "[#{Time.now}] GCODE Sent #{gcode[count].chomp} OK: #{device}"
    count += 1
    # insure we reach the end of the code file
    # fix this later to me more accurate
    # marlin firmware seems not to return "ok" for some commands 
    # or my "ok" detection needs tweaking.
    remaining_lines = (gcode_lines *1.05).to_i
    remaining_lines.times do |n|
        sleep slow_stream_down if slow_stream_down > 0.0 #slow it down a little
        begin
            test_line = sp.readline("\n")
            puts "[#{Time.now}] PRINTER Line #{count} Sent #{test_line.chomp} from GCODE #{gcode[count].chomp}"
            # test_line.downcase.chomp == 'okk' is a bandaid for a bug from Jyers marlin
            # or other unknown bug returned in 3DBenchy test
            # [2021-11-07 22:36:30 +0700] GCODE Sent Line 116446  G0 X117.456 Y112.564 -  File Progress: 73.81%
            # maybe write a routine to deal with M105
            # since the problem is the fact that the "ok" and temperature
            # string is on the same line. should be the report a newline, then "ok"
             if test_line.downcase.chomp == 'ok' || test_line.downcase.chomp == 'okk'
                
                percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
                puts "[#{Time.now}] GCODE Sent Line #{count} #{gcode[count].chomp}"
                
                if gcode[count].chomp.length > 1
                    count += 1
                    sp.write gcode[count] if gcode[count]
                    puts "[#{Time.now}] GCODE Sent Line #{count}  #{gcode[count].chomp} -  File Progress: #{percent_file_remaining}%" 
                else
                    puts "[#{Time.now}] GCODE NOT Sent Line #{count}  #{gcode[count]} -  File Progress: #{percent_file_remaining}%" 
                end
             end
            rescue
                puts "[#{Time.now}] GCODE EXCEPTION #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}%"
                if count >= gcode_lines
                    remaining_lines = count
                    add_ending_code = true
                    break
                end
                #exception occurs then nothing to read and read times out
                next
            end
        end
        
        if add_ending_code
            ending_gcode.each do |code|
                puts "[#{Time.now}] GCODE Ending Code (Extra): #{code}"
                sp.write "#{code}\r"
                sleep slow_down_final_gcode  #slow down final gcode by 100ms
            end
        end
        total_time = ((Time.now - start_time)/60.0).to_f.round(2)
        percent_file_remaining = (100.0 * count.to_f/remaining_lines.to_f ).round(2)
        puts "[#{Time.now}] GCODE Completed #{count} Lines of #{gcode_lines}  -  File Progress: #{percent_file_remaining}% Time: #{total_time} mins"
        sleep delay_before_final_close
        sp.close


    end

   def shutdown_gcode_on_sigint(sp, ending_gcode,slow_down_final_gcode = 0.1)
        puts "[#{Time.now}] SIGNAL INT Received."
        ending_gcode.each do |code|
            puts "[#{Time.now}] GCODE Ending Code (Extra): #{code}"
            sp.write "#{code}\r"
            sleep slow_down_final_gcode  #slow down final gcode by 100ms
        end
   end
1 Like

Started building out the Rails app for this project. So far, I have the following working "beta-style"

  • Test prints
  • Cancel Prints (needs firmware changes to make this work flawlessly)
  • Home Printer
  • Emergency Stop

From the original test code, I had to add methods for SIGINT and SIGTERM and fork the GCODE streaming process (so the Rails app would not freeze up during printing) and other small changes to get the methods working as a Rails project.

I will add more features for "Calibration" soon; and continue working on exception management.

Then, when all that is done, I will decide how and what JS code to add for dynamic display of temps, progress bars, etc. Not yet sure what additional JS libs I will use for this caper.

Also, need a catchy name like "RockPrint" or "RubyPrint" (those do not sound catchy to me). Maybe a mascot like name like "PenguinPrint" or "PandaPrint" or something like "BeaverPrint"... marketing and cute names is not a skill in my toolbox. Maybe "ShellPrint" since I live seaside? Or "OceanPrint" ?

Maybe simply "SeaWavePrint" ?

.... I'll go with that until something better comes along..... matches my seaside lifestyle.

"SeaWave"? "SeaSun"?, "SeaBlue"? "SeaSaw"? :slight_smile: "SeaSpotRun"? LOL, "SeaSurf"? "Sea3D"?

... back to coding..... will stick with "SeaWave" for a few days.

This is very interesting. If you ever need someone else to test this print server then i am all in. It would be quite interesting to see how much improvement this will offer compared to the octoprint install i am running.

I would be running this on a raspberry btw thats connected to the printer as i am uploading the gcode files to the rpi after generating.

2 Likes

Thanks.

I have been busy on other software development tasks since I started 'SeaWave' on Rails; but I plan to get back to it sooner or later.

1 Like