Scripts and Configuration Examples


Duet3 and Advanced Industrial Sensor Application

There are so many ways to utilize the Duet platform to integrate almost anything you can imagine for your 3D Printer, here are some examples of industrial sensor integration that can inspect printed parts and be used to calibrate pressure advance, extrusion and/or compensate for irregular movement or layer shifting. Some of these sensor interactions have proven to be extremely useful.

Note: Not all scripts/gcodes have been thoroughly tested.

Distance-setting Photoelectric Sensors

G-Code macro for configuring an Omron E3G-L1 sensor as a Z-axis probe on a Duet3 board:

; Configure sensor as Z-probe
M558 P1 C"^io2.in" H5 F120 T6000 A10 S0.005 ; Set probe type, pin, sensitivity, and travel speed
G31 X0 Y0 Z-0.1                             ; Set trigger height and offset from nozzle
M557 X15:285 Y15:285 S40                     ; Define probe grid

; Home Z-axis and probe bed
G28 Z
G30

; Save settings to config-override.g
M500

In this example, the Omron E3G-L1 sensor is connected to the io2.in pin on the Duet3 board. The M558 command sets the probe type to 1 (inductive/capacitive), the pin to ^io2.in (active low), the trigger height to 5mm, the probing speed to 120mm/min, the maximum travel speed to 6000mm/min, the probing accuracy to 10um, and the secondary Z probe to a tolerance of 0.005mm. The G31 command sets the trigger height to -0.1mm (i.e. the nozzle is 0.1mm above the bed when triggered) and sets the offset from the nozzle to X0 Y0. The M557 command defines the probe grid as a square from X15 Y15 to X285 Y285 with a spacing of 40mm.

Finally, the script homes the Z-axis and probes the bed using the configured sensor. The settings are then saved to the config-override.g file for persistence across power cycles.

Bed Levelling using Duetwebapi and Python:

import requests
import numpy as np

# IP address of the Duet controller
duet_ip = "192.168.1.10"

# Connect to the Duet controller
r = requests.get("http://{}/heightmap.csv".format(duet_ip))
response = r.text

# Split response into rows and remove header
rows = response.split("\n")[1:]

# Parse the data into a numpy array
heightmap = np.zeros((len(rows), len(rows)))
for i, row in enumerate(rows):
    values = row.split(",")
    for j, value in enumerate(values):
        heightmap[i][j] = float(value)

# Print the heightmap
print(heightmap)

This script sends a GET request to the Duet controller to retrieve the height-map data, which is returned as a CSV file. The script then parses the CSV data into a numpy array and prints it to the console.

Note that the IP address of the Duet controller will need to be updated to match the IP address of your own controller. Additionally, the script assumes that the heightmap.csv file is accessible via the /heightmap.csv endpoint on the controller. If your controller is configured differently, you may need to modify the script accordingly.

Python script that utilizes duetwebapi to verify First Layer Completion:

import requests
import time

# Duet control board IP address
duet_ip = '192.168.1.10'

# Duet web control API endpoint for reading G-code responses
duet_api = f'http://{duet_ip}/rr_gcode'

# Duet web control API endpoint for reading height map CSV data
duet_heightmap_api = f'http://{duet_ip}/heightmap.csv'

# Photoelectric sensor minimum distance threshold (in mm) for first layer detection
sensor_distance_threshold = 1.0

# G-code command to initiate bed leveling and generate height map data
bed_leveling_gcode = 'G32'

# G-code command to set the Z-height offset for the photoelectric sensor
set_sensor_offset_gcode = 'G31 Z'

# G-code command to query the current Z-height of the printer head
get_current_z_height_gcode = 'M114'

# G-code command to move the printer head to a specified Z-height
move_to_z_height_gcode = 'G1 Z'

# Define the desired Z-height for the photoelectric sensor to detect the first layer
first_layer_z_height = 0.2

# Wait for the printer to finish initialization
time.sleep(30)

# Send G-code command to initiate bed leveling and generate height map data
requests.get(f'{duet_api}?gcode={bed_leveling_gcode}')

# Wait for bed leveling to complete and height map data to be generated
time.sleep(60)

# Send G-code command to set the Z-height offset for the photoelectric sensor
requests.get(f'{duet_api}?gcode={set_sensor_offset_gcode}{first_layer_z_height}')

# Send G-code command to move the printer head to the desired Z-height for the photoelectric sensor
requests.get(f'{duet_api}?gcode={move_to_z_height_gcode}{first_layer_z_height}')

# Wait for the printer head to move to the desired Z-height for the photoelectric sensor
time.sleep(10)

# Send G-code command to query the current Z-height of the printer head
response = requests.get(f'{duet_api}?gcode={get_current_z_height_gcode}')

# Extract the current Z-height from the G-code response
current_z_height = float(response.text.split('Z:')[1].split(' ')[0])

# Wait for the photoelectric sensor to stabilize and take a reading
time.sleep(5)

# Send G-code command to query the current Z-height of the printer head again
response = requests.get(f'{duet_api}?gcode={get_current_z_height_gcode}')

# Extract the current Z-height from the G-code response again
new_z_height = float(response.text.split('Z:')[1].split(' ')[0])

# If the current Z-height is within the distance threshold of the desired first layer height,
# then the first layer has been successfully printed
if abs(new_z_height - current_z_height) < sensor_distance_threshold:
    print('First layer printed successfully!')
else:
    print('First layer not printed yet.')

Note: This script assumes that the photoelectric sensor is connected to the printer control board and has been properly calibrated to detect the distance threshold for the first layer. It also assumes that the printer has already been levelled and a height map has been generated prior to running the script.

Python script that utilizes duetwebapi to scan every 15th layer of a printing part to verify the layer has printed correctly:

import requests
import time

# IP address of Duet controller
duet_ip = "192.168.1.100"

# Port number of Duet Web Control
duet_port = "80"

# Height of each layer in millimeters
layer_height = 0.2

# Layer number to start scanning from
start_layer = 15

# Number of layers to scan
num_layers = 10

# Distance from the sensor to the print bed in millimeters
sensor_height = 2.0

# Minimum and maximum distances to trigger a detection in millimeters
min_distance = 0.5
max_distance = 1.5

# Connect to the Duet Web Control API
def connect():
    url = "http://" + duet_ip + ":" + duet_port + "/rr_connect"
    response = requests.get(url)
    if response.status_code != 200:
        print("Failed to connect to Duet Web Control")
        return None
    return response.cookies

# Disconnect from the Duet Web Control API
def disconnect(cookies):
    url = "http://" + duet_ip + ":" + duet_port + "/rr_disconnect"
    requests.get(url, cookies=cookies)

# Move the print head to the specified layer
def move_to_layer(layer):
    z_pos = layer * layer_height
    url = "http://" + duet_ip + ":" + duet_port + "/rr_gcode?gcode=G0%20Z" + str(z_pos)
    response = requests.get(url, cookies=cookies)
    if response.status_code != 200:
        print("Failed to move to layer", layer)

# Read the distance value from the sensor
def read_sensor():
    url = "http://" + duet_ip + ":" + duet_port + "/rr_gcode?gcode=M408%20P0%20R1200"
    response = requests.get(url, cookies=cookies)
    if response.status_code != 200:
        print("Failed to read sensor")
        return None
    value = response.json()['result'][0]['dist']
    return value

# Scan the specified layer for a successful print
def scan_layer(layer):
    move_to_layer(layer)
    time.sleep(1)
    value = read_sensor()
    if value is None:
        return False
    if value > min_distance and value < max_distance:
        return True
    else:
        return False

# Main program loop
if __name__ == "__main__":
    cookies = connect()
    if cookies is None:
        exit()
    for i in range(start_layer, start_layer + num_layers):
        if scan_layer(i):
            print("Layer", i, "printed successfully")
        else:
            print("Layer", i, "failed to print")
    disconnect(cookies)

This script connects to the Duet controller through its web API, moves the print head to each layer to be scanned, reads the distance value from the Omron E3G-L1 sensor, and checks if the distance value is within the acceptable range to indicate a successful print. The script then prints whether each scanned layer was successful or not.

Note: This script assumes that the sensor is already connected to the Duet controller and configured correctly.

Gcode macro for configuring an Omron E2A proximity sensor as a Z-axis probe on a Duet 3 board:

; Configure probe
M558 P1 C"^io0.in" H5 F120 T5000          ; set probe type to switch, use io0.in pin, dive height 5mm, speed 120mm/min, travel 5000mm/min
G31 P500 X0 Y0 Z-0.1                      ; set trigger threshold and offsets
M557 X50:250 Y50:250 S50                  ; define probe grid

; Home Z-axis
G91                                      ; relative positioning
G1 H2 Z5 F1200                            ; lift Z-axis by 5mm at 1200mm/min
G90                                      ; absolute positioning
G1 X0 Y0 F12000                           ; move to the center of the bed
G30                                      ; perform single Z-probe

; Save settings
M500                                     ; save settings to config-override.g

In this example, the Omron E2A proximity sensor is connected to the io0.in pin on the Duet 3 board. The M558 command sets the probe type to switch, with a dive height of 5mm, a speed of 120mm/min, and a travel speed of 5000mm/min. The G31 command sets the trigger threshold and offsets for the probe. The M557 command defines the probe grid.

The macro then homes the Z-axis, lifts it by 5mm, moves to the center of the bed, and performs a single Z-probe. Finally, the M500 command is used to save the settings to config-override.g.

Commercial/Consumer Grade Sensors

ADXL345 Accelerometer

The Adxl345 is a pretty inexpensive accelerometer, so it’s not always going to be most accurate, however we can use filtering techniques to improve performance and reliability.

Here’s an example Python script that utilizes the DuetWebAPI to read sensor data from an ADXL345 to create a resonance profile of a moving mass and compensate for any irregularities in movement during high speed printing:

import requests
import time
from math import pi, sqrt, sin

# Define the IP address of the printer
duet_ip = "192.168.1.100"

# Define the printer's API endpoints for reading the accelerometer data
accel_endpoint = "http://{}/rr/accelerometer".format(duet_ip)

# Define the printer's API endpoints for setting the movement compensation parameters
comp_endpoint = "http://{}/rr/compensation".format(duet_ip)

# Define the printer's acceleration parameters
max_accel = 500  # in mm/s^2
max_jerk = 200  # in mm/s^3

# Define the printer's maximum print speed
max_speed = 100  # in mm/s

# Define the accelerometer data buffer size
buffer_size = 50

# Define the frequency range to use for the resonance profile
freq_range = (1, 20)  # in Hz

# Define the number of frequency steps to use for the resonance profile
freq_steps = 20

# Define the amplitude threshold for detecting resonance
resonance_threshold = 0.1  # in g

# Define the time to wait between acceleration readings
read_interval = 0.01  # in seconds

# Define the function to read the accelerometer data from the printer
def read_accel_data():
    response = requests.get(accel_endpoint)
    data = response.json()
    accel = data["accel"]
    return accel

# Define the function to calculate the resonance profile
def calc_resonance_profile():
    freq_step = (freq_range[1] - freq_range[0]) / freq_steps
    freqs = [(freq_range[0] + (i * freq_step)) for i in range(freq_steps)]
    accel_data = []
    for i in range(buffer_size):
        accel = read_accel_data()
        accel_data.append(sqrt(sum([a**2 for a in accel])))
    accel_data = accel_data[-buffer_size:]
    max_amp = max(accel_data)
    if max_amp < resonance_threshold:
        return None
    profile = []
    for freq in freqs:
        w = 2 * pi * freq
        amp = sum([accel_data[i] * sin(w * (i * read_interval)) for i in range(buffer_size)])
        profile.append(amp / max_amp)
    return profile

# Define the main function for the program
def main():
    # Calculate the resonance profile
    profile = calc_resonance_profile()
    if profile is None:
        return
    print("Resonance profile:", profile)

    # Calculate the compensation parameters
    max_comp = (max_accel / max_speed) * (1 / max_speed) * (1 / max_jerk)
    comp_params = [(profile[i] * max_comp) for i in range(len(profile))]
    print("Compensation parameters:", comp_params)

    # Send the compensation parameters to the printer
    response = requests.post(comp_endpoint, json={"compensation": comp_params})
    print("Compensation response:", response.text)

# Run the main function
if __name__ == "__main__":
    main()

This script first defines the IP address of the printer and the API endpoints for reading the accelerometer data and setting the movement compensation parameters. It then defines the acceleration parameters and maximum print speed, as well as the accelerometer data buffer size, frequency range, number of frequency steps, amplitude threshold for detecting resonance, and time to wait.

Linear Rail Tester Using Adxl345

In this example Python script that utilizes the DuetWebAPI to move a specified axis back and forth along 80% of the axis, progressively getting faster and read sensor data from an ADXL345 to find deviations in the axis and compensate for it:

import requests
import time
import math

# Define the IP address of the printer and the axis to move
printer_ip = '192.168.1.100'
axis = 'X'

# Define the maximum speed and acceleration
max_speed = 3000
max_acceleration = 10000

# Define the number of cycles to run
num_cycles = 10

# Define the endpoint for sending G-code commands
duet_endpoint = f"http://{printer_ip}/rr_gcode"

# Define the endpoint for reading the accelerometer data
accel_endpoint = f"http://{printer_ip}/rr_sensor?name=accel"

# Define the starting and ending positions for the axis
start_pos = 0
end_pos = requests.get(f"http://{printer_ip}/rr_status?type=3").json()[axis]["coords"]

# Calculate the distance to move and the increment per cycle
move_distance = 0.8 * (end_pos - start_pos)
increment = move_distance / num_cycles

# Define the G-code command to move the axis
move_command = f"G1 {axis}{start_pos} F1000"

# Send the move command to the printer
requests.post(duet_endpoint, data=move_command)

# Wait for the move to complete
time.sleep(2)

# Start moving the axis back and forth
for i in range(num_cycles):
    # Calculate the current position of the axis
    current_pos = start_pos + (i * increment)
    
    # Calculate the current speed and acceleration
    current_speed = math.sqrt(current_pos / move_distance) * max_speed
    current_acceleration = math.sqrt(current_pos / move_distance) * max_acceleration
    
    # Define the G-code command to move the axis
    move_command = f"G1 {axis}{current_pos} F{current_speed} A{current_acceleration}"
    
    # Send the move command to the printer
    requests.post(duet_endpoint, data=move_command)
    
    # Wait for the move to complete
    time.sleep(2)
    
    # Read the accelerometer data
    accel_data = requests.get(accel_endpoint).json()
    
    # Calculate the deviation in the axis based on the accelerometer data
    axis_deviation = accel_data[axis] * 0.000244
    
    # Compensate for the deviation by adjusting the position of the axis
    current_pos -= axis_deviation
    
    # Define the G-code command to move the axis to compensate for the deviation
    compensate_command = f"G1 {axis}{current_pos} F{current_speed} A{current_acceleration}"
    
    # Send the compensate command to the printer
    requests.post(duet_endpoint, data=compensate_command)
    
    # Wait for the compensate move to complete
    time.sleep(2)

# Move the axis back to the starting position
move_command = f"G1 {axis}{start_pos} F1000"
requests.post(duet_endpoint, data=move_command)

Note: This script assumes that you have an ADXL345 connected to the printer and that the printer supports the DuetWebAPI. You may need to modify the IP address and axis variables to match your printer configuration. Additionally, you may need to adjust the max_speed, max_acceleration, and num_cycles variables based on your specific needs.

More coming soon, this page is always being updated.

Leave a Comment

Your email address will not be published. Required fields are marked *