def __init_path_planner(self): self.native_planner = PathPlannerNative(int(self.printer.move_cache_size)) fw0 = self.pru_firmware.get_firmware(0) fw1 = self.pru_firmware.get_firmware(1) if fw0 is None or fw1 is None: return self.native_planner.initPRU(fw0, fw1) self.native_planner.setAxisStepsPerMeter(tuple(self.printer.steps_pr_meter)) self.native_planner.setMaxSpeeds(tuple(self.printer.max_speeds)) self.native_planner.setMinSpeeds(tuple(self.printer.min_speeds)) self.native_planner.setAcceleration(tuple(self.printer.acceleration)) self.native_planner.setJerks(tuple(self.printer.jerks)) self.native_planner.setPrintMoveBufferWait(int(self.printer.print_move_buffer_wait)) self.native_planner.setMinBufferedMoveTime(int(self.printer.min_buffered_move_time)) self.native_planner.setMaxBufferedMoveTime(int(self.printer.max_buffered_move_time)) self.native_planner.setSoftEndstopsMin(tuple(self.printer.soft_min)) self.native_planner.setSoftEndstopsMax(tuple(self.printer.soft_max)) self.native_planner.setBedCompensationMatrix(tuple(self.printer.matrix_bed_comp.ravel())) self.native_planner.setMaxPathLength(self.printer.max_length) self.native_planner.setAxisConfig(self.printer.axis_config) self.native_planner.delta_bot.setMainDimensions(Delta.Hez, Delta.L, Delta.r) self.native_planner.delta_bot.setEffectorOffset(Delta.Ae, Delta.Be, Delta.Ce) self.native_planner.delta_bot.setRadialError(Delta.A_radial, Delta.B_radial, Delta.C_radial); self.native_planner.delta_bot.setTangentError(Delta.A_tangential, Delta.B_tangential, Delta.C_tangential) self.native_planner.delta_bot.recalculate() self.native_planner.enableSlaves(self.printer.has_slaves) if self.printer.has_slaves: for master in Printer.AXES: slave = self.printer.slaves[master] if slave: master_index = Printer.axis_to_index(master) slave_index = Printer.axis_to_index(slave) self.native_planner.addSlave(int(master_index), int(slave_index)) logging.debug("Axis " + str(slave_index) + " is slaved to axis " + str(master_index)) self.native_planner.setBacklashCompensation(tuple(self.printer.backlash_compensation)); self.native_planner.setState(self.prev.end_pos) self.printer.plugins.path_planner_initialized(self) self.native_planner.runThread()
def configure_slaves(self): self.native_planner.enableSlaves(self.printer.has_slaves) if self.printer.has_slaves: for master in Printer.AXES: slave = self.printer.slaves[master] if slave: master_index = Printer.axis_to_index(master) slave_index = Printer.axis_to_index(slave) self.native_planner.addSlave(int(master_index), int(slave_index)) logging.debug("Axis " + str(slave_index) + " is slaved to axis " + str(master_index))
def _go_to_home(self, axis): """ go to the designated home position do this as a separate call from _home_internal due to delta platforms performing home in cartesian mode """ path_home = {} speed = self.printer.home_speed[0] accel = self.printer.acceleration[0] for a in axis: path_home[a] = self.home_pos[a] speed = min(abs(speed), abs(self.printer.home_speed[Printer.axis_to_index(a)])) logging.debug("Home: %s" % path_home) # Move to home position p = AbsolutePath(path_home, speed, accel, True, False, False, False) self.add_path(p) self.wait_until_done() # Due to rounding errors, we explicitly set the found # position to the right value. # Reset (final) position to offset p = G92Path(path_home) self.add_path(p) return
def execute(self, g): for i in range(g.num_tokens()): # Run through all tokens axis = g.token_letter(i) # Get the axis, X, Y, Z or E value = float(g.token_value(i)) if value > 0: logging.info("Updating steps pr mm on {} to {}".format( axis, value)) self.printer.steppers[axis].set_steps_pr_mm(value) i = Printer.axis_to_index(axis) self.printer.steps_pr_meter[i] = self.printer.steppers[ axis].get_steps_pr_meter() else: logging.error('Steps per milimeter must be grater than zero.') self.printer.path_planner.restart()
def add_path(self, new): """ Add a path segment to the path planner """ """ This code, and the native planner, needs to be updated for reach. """ # Link to the previous segment in the chain new.set_prev(self.prev) # NOTE: printing the added path slows things down SIGNIFICANTLY #logging.debug("path added: "+ str(new)) if new.is_G92(): self.native_planner.setState(tuple(new.end_pos)) elif new.needs_splitting(): #TODO: move this to C++ # this branch splits up any G2 or G3 movements (arcs) # should be moved to C++ as it is math heavy # need to convert it to linear segments before feeding to the queue # as we want to keep the queue only dealing with linear stuff for simplicity for seg in new.get_segments(): self.add_path(seg) else: self.printer.ensure_steppers_enabled() optimize = new.movement != Path.RELATIVE tool_axis = Printer.axis_to_index(self.printer.current_tool) self.native_planner.setAxisConfig(int(self.printer.axis_config)) # Start_pos is unused. TODO: Remove it. # Bed matrix behaviour is handled in Python space, it is fast enough for that. self.native_planner.queueMove( (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0), #tuple(new.start_pos), tuple(new.end_pos), new.speed, new.accel, bool(new.cancelable), bool(optimize), bool(new.enable_soft_endstops), False, #bool(new.use_bed_matrix), bool(new.use_backlash_compensation), int(tool_axis), True) self.prev = new self.prev.unlink() # We don't want to store the entire print # in memory, so we keep only the last path. # make sure that the current state of the printer is correct self.prev.end_pos = self.native_planner.getState()
def add_path(self, new): """ Add a path segment to the path planner """ """ This code, and the native planner, needs to be updated for reach. """ # Link to the previous segment in the chain new.set_prev(self.prev) # NOTE: printing the added path slows things down SIGNIFICANTLY #logging.debug("path added: "+ str(new)) if new.is_G92(): self.native_planner.setState(tuple(new.end_pos)) elif new.needs_splitting(): #TODO: move this to C++ # this branch splits up any G2 or G3 movements (arcs) # should be moved to C++ as it is math heavy # need to convert it to linear segments before feeding to the queue # as we want to keep the queue only dealing with linear stuff for simplicity for seg in new.get_segments(): self.add_path(seg) else: self.printer.ensure_steppers_enabled() optimize = new.movement != Path.RELATIVE tool_axis = Printer.axis_to_index(self.printer.current_tool) self.native_planner.setAxisConfig(int(self.printer.axis_config)) self.native_planner.queueMove(tuple(new.start_pos), tuple(new.end_pos), new.speed, new.accel, bool(new.cancelable), bool(optimize), bool(new.enable_soft_endstops), bool(new.use_bed_matrix), bool(new.use_backlash_compensation), int(tool_axis), True) self.prev = new self.prev.unlink() # We don't want to store the entire print
def _home_internal(self, axis): """ Private method for homing a set or a single axis """ logging.debug("homing internal " + str(axis)) path_search = {} path_backoff = {} path_fine_search = {} path_center = {} path_zero = {} speed = self.printer.home_speed[0] # TODO: speed for each axis accel = self.printer.acceleration[0] # TODO: accel for each axis for a in axis: if not self.printer.steppers[a].has_endstop: logging.debug("Skipping homing for " + str(a)) continue logging.debug("Doing homing for " + str(a)) if self.printer.home_speed[Printer.axis_to_index(a)] < 0: # Search to positive ends path_search[a] = self.travel_length[a] path_center[a] = self.center_offset[a] else: # Search to negative ends path_search[a] = -self.travel_length[a] path_center[a] = -self.center_offset[a] backoff_length = -np.sign( path_search[a]) * self.printer.home_backoff_offset[ Printer.axis_to_index(a)] path_backoff[a] = backoff_length path_fine_search[a] = -backoff_length * 1.2 speed = min(abs(speed), abs(self.printer.home_speed[Printer.axis_to_index(a)])) accel = min(accel, self.printer.acceleration[Printer.axis_to_index(a)]) fine_search_speed = min( abs(speed), abs(self.printer.home_backoff_speed[Printer.axis_to_index(a)])) logging.debug("Search: %s at %s m/s, %s m/s^2" % (path_search, speed, accel)) logging.debug("Backoff to: %s" % path_backoff) logging.debug("Fine search: %s" % path_fine_search) logging.debug("Center: %s" % path_center) # Move until endstop is hit p = RelativePath(path_search, speed, accel, True, False, True, False) self.add_path(p) self.wait_until_done() logging.debug("Coarse search done!") # Reset position to offset p = G92Path(path_center) self.add_path(p) self.wait_until_done() # Back off a bit p = RelativePath(path_backoff, speed, accel, True, False, True, False) self.add_path(p) # Hit the endstop slowly p = RelativePath(path_fine_search, fine_search_speed, accel, True, False, True, False) self.add_path(p) self.wait_until_done() # Reset (final) position to offset p = G92Path(path_center) self.add_path(p) return path_center, speed
def make_config_file(self): # Create a config file configFile_0 = os.path.join("/tmp", 'config.h') with open(configFile_0, 'w') as configFile: # GPIO banks banks = {"0": 0, "1": 0, "2": 0, "3": 0} step_banks = {"0": 0, "1": 0, "2": 0, "3": 0} dir_banks = {"0": 0, "1": 0, "2": 0, "3": 0} direction_mask = 0 # Define step and dir pins for name, stepper in self.printer.steppers.iteritems(): step_pin = str(stepper.get_step_pin()) step_bank = str(stepper.get_step_bank()) dir_pin = str(stepper.get_dir_pin()) dir_bank = str(stepper.get_dir_bank()) configFile.write('#define STEPPER_' + name + '_STEP_BANK\t\t' + "STEPPER_GPIO_" + step_bank + '\n') configFile.write('#define STEPPER_' + name + '_STEP_PIN\t\t' + step_pin + '\n') configFile.write('#define STEPPER_' + name + '_DIR_BANK\t\t' + "STEPPER_GPIO_" + dir_bank + '\n') configFile.write('#define STEPPER_' + name + '_DIR_PIN\t\t' + dir_pin + '\n') # Define direction direction = "0" if self.config.getint( 'Steppers', 'direction_' + name) > 0 else "1" configFile.write('#define STEPPER_' + name + '_DIRECTION\t\t' + direction + '\n') index = Printer.axis_to_index(name) direction_mask |= (int(direction) << index) # Generate the GPIO bank masks banks[step_bank] |= (1 << int(step_pin)) banks[dir_bank] |= (1 << int(dir_pin)) step_banks[step_bank] |= (1 << int(step_pin)) dir_banks[dir_bank] |= (1 << int(dir_pin)) configFile.write('#define DIRECTION_MASK ' + bin(direction_mask) + '\n') configFile.write('\n') # Define end stop pins and banks for name, endstop in self.printer.end_stops.iteritems(): bank, pin = endstop.get_gpio_bank_and_pin() configFile.write('#define STEPPER_' + name + '_END_PIN\t\t' + str(pin) + '\n') configFile.write('#define STEPPER_' + name + '_END_BANK\t\t' + "GPIO_" + str(bank) + '_IN\n') configFile.write('\n') # Construct the end stop inversion mask inversion_mask = "#define INVERSION_MASK\t\t0b00" for name in ["Z2", "Y2", "X2", "Z1", "Y1", "X1"]: inversion_mask += "1" if self.config.getboolean( 'Endstops', 'invert_' + name) else "0" configFile.write(inversion_mask + "\n") # Construct the endstop lookup table. for name, endstop in self.printer.end_stops.iteritems(): mask = 0 # stepper name is x_cw or x_ccw option = 'end_stop_' + name + '_stops' for stepper in self.config.get('Endstops', option).split(","): stepper = stepper.strip() if stepper == "": continue m = re.search('^([xyzehabc])_(ccw|cw|pos|neg)$', stepper) if (m == None): raise RuntimeError("'" + stepper + "' is invalid for " + option) # direction should be 1 for normal operation and -1 to invert the stepper. if (m.group(2) == "pos"): direction = -1 elif (m.group(2) == "neg"): direction = 1 else: direction = 1 if self.config.getint( 'Steppers', 'direction_' + stepper[0]) > 0 else -1 if (m.group(2) == "ccw"): direction *= -1 cur = 1 << ("xyzehabc".index(m.group(1))) if (direction == -1): cur <<= 8 mask += cur logging.debug("Endstop {0} mask = {1}".format(name, bin(mask))) bin_mask = "0b" + (bin(mask)[2:]).zfill(16) configFile.write("#define STEPPER_MASK_" + name + "\t\t" + bin_mask + "\n") configFile.write("\n") # Put each dir and step pin in the proper buck if they are for GPIO0 or GPIO1 bank. # This is a restriction due to the limited capabilities of the pasm preprocessor. for name, bank in banks.iteritems(): #bank = (~bank & 0xFFFFFFFF) configFile.write("#define GPIO" + name + "_MASK\t\t" + bin(bank) + "\n") #for name, bank in step_banks.iteritems(): #bank = (~bank & 0xFFFFFFFF) # configFile.write("#define GPIO"+name+"_STEP_MASK\t\t" +bin(bank)+ "\n"); for name, bank in dir_banks.iteritems(): #bank = (~bank & 0xFFFFFFFF) configFile.write("#define GPIO" + name + "_DIR_MASK\t\t" + bin(bank) + "\n") configFile.write("\n") # Add end stop delay to the config file end_stop_delay = self.config.getint('Endstops', 'end_stop_delay_cycles') configFile.write("#define END_STOP_DELAY " + str(end_stop_delay) + "\n") return configFile_0
def _home_internal(self, axis): """ Private method for homing a set or a single axis """ logging.debug("homing internal " + str(axis)) path_search = {} path_backoff = {} path_fine_search = {} path_center = {} path_zero = {} speed = self.printer.home_speed[0] # TODO: speed for each axis accel = self.printer.acceleration[0] # TODO: accel for each axis for a in axis: if not self.printer.steppers[a].has_endstop: logging.debug("Skipping homing for " + str(a)) continue logging.debug("Doing homing for " + str(a)) if self.printer.home_speed[Printer.axis_to_index(a)] < 0: # Search to positive ends path_search[a] = self.travel_length[a] path_center[a] = self.center_offset[a] else: # Search to negative ends path_search[a] = -self.travel_length[a] path_center[a] = -self.center_offset[a] backoff_length = -np.sign(path_search[a]) * self.printer.home_backoff_offset[Printer.axis_to_index(a)] path_backoff[a] = backoff_length; path_fine_search[a] = -backoff_length * 1.2; speed = min(abs(speed), abs(self.printer.home_speed[Printer.axis_to_index(a)])) accel = min(accel, self.printer.acceleration[Printer.axis_to_index(a)]) fine_search_speed = min(abs(speed), abs(self.printer.home_backoff_speed[Printer.axis_to_index(a)])) logging.debug("Search: %s at %s m/s, %s m/s^2" % (path_search, speed, accel)) logging.debug("Backoff to: %s" % path_backoff) logging.debug("Fine search: %s" % path_fine_search) logging.debug("Center: %s" % path_center) # Move until endstop is hit p = RelativePath(path_search, speed, accel, True, False, True, False) self.add_path(p) self.wait_until_done() logging.debug("Coarse search done!") # Reset position to offset p = G92Path(path_center) self.add_path(p) self.wait_until_done() # Back off a bit p = RelativePath(path_backoff, speed, accel, True, False, True, False) self.add_path(p) # Hit the endstop slowly p = RelativePath(path_fine_search, fine_search_speed, accel, True, False, True, False) self.add_path(p) self.wait_until_done() # Reset (final) position to offset p = G92Path(path_center) self.add_path(p) return path_center, speed
def make_config_file(self): # Create a config file configFile_0 = os.path.join("/tmp", 'config.h') with open(configFile_0, 'w') as configFile: # GPIO banks banks = {"0": 0, "1": 0, "2": 0, "3": 0} step_banks = {"0": 0, "1": 0, "2": 0, "3": 0} dir_banks = {"0": 0, "1": 0, "2": 0, "3": 0} direction_mask = 0 # Define step and dir pins for name, stepper in self.printer.steppers.iteritems(): step_pin = str(stepper.get_step_pin()) step_bank = str(stepper.get_step_bank()) dir_pin = str(stepper.get_dir_pin()) dir_bank = str(stepper.get_dir_bank()) configFile.write('#define STEPPER_' + name + '_STEP_BANK\t\t' + "STEPPER_GPIO_"+step_bank+'\n') configFile.write('#define STEPPER_' + name + '_STEP_PIN\t\t' + step_pin+'\n') configFile.write('#define STEPPER_' + name + '_DIR_BANK\t\t' + "STEPPER_GPIO_"+dir_bank+'\n') configFile.write('#define STEPPER_' + name + '_DIR_PIN\t\t' + dir_pin+'\n') # Define direction direction = "0" if self.config.getint('Steppers', 'direction_' + name) > 0 else "1" configFile.write('#define STEPPER_'+ name +'_DIRECTION\t\t'+ direction +'\n') index = Printer.axis_to_index(name) direction_mask |= (int(direction) << index) # Generate the GPIO bank masks banks[step_bank] |= (1<<int(step_pin)) banks[dir_bank] |= (1<<int(dir_pin)) step_banks[step_bank] |= (1<<int(step_pin)) dir_banks[dir_bank] |= (1<<int(dir_pin)) configFile.write('#define DIRECTION_MASK '+bin(direction_mask)+'\n') configFile.write('\n') # Define end stop pins and banks for name, endstop in self.printer.end_stops.iteritems(): bank, pin = endstop.get_gpio_bank_and_pin() configFile.write('#define STEPPER_'+ name +'_END_PIN\t\t'+ str(pin) +'\n') configFile.write('#define STEPPER_'+ name +'_END_BANK\t\t'+ "GPIO_"+str(bank) +'_IN\n') configFile.write('\n') # Construct the end stop inversion mask inversion_mask = "#define INVERSION_MASK\t\t0b00" for name in ["Z2", "Y2", "X2", "Z1", "Y1", "X1"]: inversion_mask += "1" if self.config.getboolean('Endstops', 'invert_' + name) else "0" configFile.write(inversion_mask + "\n"); # Construct the endstop lookup table. for name, endstop in self.printer.end_stops.iteritems(): mask = 0 # stepper name is x_cw or x_ccw option = 'end_stop_' + name + '_stops' for stepper in self.config.get('Endstops', option).split(","): stepper = stepper.strip() if stepper == "": continue m = re.search('^([xyzehabc])_(ccw|cw|pos|neg)$', stepper) if (m == None): raise RuntimeError("'" + stepper + "' is invalid for " + option) # direction should be 1 for normal operation and -1 to invert the stepper. if (m.group(2) == "pos"): direction = -1 elif (m.group(2) == "neg"): direction = 1 else: direction = 1 if self.config.getint('Steppers', 'direction_' + stepper[0]) > 0 else -1 if (m.group(2) == "ccw"): direction *= -1 cur = 1 << ("xyzehabc".index(m.group(1))) if (direction == -1): cur <<= 8 mask += cur logging.debug("Endstop {0} mask = {1}".format(name, bin(mask))) bin_mask = "0b"+(bin(mask)[2:]).zfill(16) configFile.write("#define STEPPER_MASK_" + name + "\t\t" + bin_mask + "\n") configFile.write("\n"); # Put each dir and step pin in the proper buck if they are for GPIO0 or GPIO1 bank. # This is a restriction due to the limited capabilities of the pasm preprocessor. for name, bank in banks.iteritems(): #bank = (~bank & 0xFFFFFFFF) configFile.write("#define GPIO"+name+"_MASK\t\t" +bin(bank)+ "\n"); #for name, bank in step_banks.iteritems(): #bank = (~bank & 0xFFFFFFFF) # configFile.write("#define GPIO"+name+"_STEP_MASK\t\t" +bin(bank)+ "\n"); for name, bank in dir_banks.iteritems(): #bank = (~bank & 0xFFFFFFFF) configFile.write("#define GPIO"+name+"_DIR_MASK\t\t" +bin(bank)+ "\n"); configFile.write("\n"); # Add end stop delay to the config file end_stop_delay = self.config.getint('Endstops', 'end_stop_delay_cycles') configFile.write("#define END_STOP_DELAY " +str(end_stop_delay)+ "\n"); return configFile_0
def __init__(self, config_location="/etc/redeem"): """ config_location: provide the location to look for config files. - default is installed directory - allows for running in a local directory when debugging """ firmware_version = "1.2.8~Predator" logging.info("Redeem initializing " + firmware_version) printer = Printer() self.printer = printer Path.printer = printer printer.firmware_version = firmware_version printer.config_location = config_location # Set up and Test the alarm framework Alarm.printer = self.printer Alarm.executor = AlarmExecutor() alarm = Alarm(Alarm.ALARM_TEST, "Alarm framework operational") # check for config files file_path = os.path.join(config_location, "default.cfg") if not os.path.exists(file_path): logging.error( file_path + " does not exist, this file is required for operation") sys.exit() # maybe use something more graceful? local_path = os.path.join(config_location, "local.cfg") if not os.path.exists(local_path): logging.info(local_path + " does not exist, Creating one") os.mknod(local_path) os.chmod(local_path, 0o777) # Parse the config files. printer.config = CascadingConfigParser([ os.path.join(config_location, 'default.cfg'), os.path.join(config_location, 'printer.cfg'), os.path.join(config_location, 'local.cfg') ]) # Check the local and printer files printer_path = os.path.join(config_location, "printer.cfg") if os.path.exists(printer_path): printer.config.check(printer_path) printer.config.check(os.path.join(config_location, 'local.cfg')) # Get the revision and loglevel from the Config file level = self.printer.config.getint('System', 'loglevel') if level > 0: logging.getLogger().setLevel(level) # Set up additional logging, if present: if self.printer.config.getboolean('System', 'log_to_file'): logfile = self.printer.config.get('System', 'logfile') formatter = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s' printer.redeem_logging_handler = logging.handlers.RotatingFileHandler( logfile, maxBytes=2 * 1024 * 1024) printer.redeem_logging_handler.setFormatter( logging.Formatter(formatter)) printer.redeem_logging_handler.setLevel(level) logging.getLogger().addHandler(printer.redeem_logging_handler) logging.info("-- Logfile configured --") # Find out which capes are connected self.printer.config.parse_capes() self.revision = self.printer.config.replicape_revision if self.revision: logging.info("Found Replicape rev. " + self.revision) printer.replicape_key = printer.config.get_key() else: logging.warning("Oh no! No Replicape present!") self.revision = "0B3A" # We set it to 5 axis by default Printer.NUM_AXES = 5 if self.printer.config.reach_revision: logging.info("Found Reach rev. " + self.printer.config.reach_revision) if self.printer.config.reach_revision == "00A0": Printer.NUM_AXES = 8 elif self.printer.config.reach_revision == "00B0": Printer.NUM_AXES = 7 if self.revision in ["00A4", "0A4A", "00A3"]: PWM.set_frequency(100) elif self.revision in ["00B1", "00B2", "00B3", "0B3A"]: PWM.set_frequency(printer.config.getint('Cold-ends', 'pwm_freq')) # Init the Watchdog timer printer.watchdog = Watchdog() # Enable PWM and steppers printer.enable = Enable("P9_41") printer.enable.set_disabled() # Init the Paths printer.axis_config = printer.config.getint('Geometry', 'axis_config') # Init the end stops EndStop.inputdev = self.printer.config.get("Endstops", "inputdev") # Set up key listener Key_pin.listener = Key_pin_listener(EndStop.inputdev) homing_only_endstops = self.printer.config.get('Endstops', 'homing_only_endstops') for es in ["Z2", "Y2", "X2", "Z1", "Y1", "X1"]: # Order matches end stop inversion mask in Firmware pin = self.printer.config.get("Endstops", "pin_" + es) keycode = self.printer.config.getint("Endstops", "keycode_" + es) invert = self.printer.config.getboolean("Endstops", "invert_" + es) self.printer.end_stops[es] = EndStop(printer, pin, keycode, es, invert) self.printer.end_stops[es].stops = self.printer.config.get( 'Endstops', 'end_stop_' + es + '_stops') # activate all the endstops self.printer.set_active_endstops() # Init the 5 Stepper motors (step, dir, fault, DAC channel, name) Stepper.printer = printer if self.revision == "00A3": printer.steppers["X"] = Stepper_00A3("GPIO0_27", "GPIO1_29", "GPIO2_4", 0, "X") printer.steppers["Y"] = Stepper_00A3("GPIO1_12", "GPIO0_22", "GPIO2_5", 1, "Y") printer.steppers["Z"] = Stepper_00A3("GPIO0_23", "GPIO0_26", "GPIO0_15", 2, "Z") printer.steppers["E"] = Stepper_00A3("GPIO1_28", "GPIO1_15", "GPIO2_1", 3, "E") printer.steppers["H"] = Stepper_00A3("GPIO1_13", "GPIO1_14", "GPIO2_3", 4, "H") elif self.revision == "00B1": printer.steppers["X"] = Stepper_00B1("GPIO0_27", "GPIO1_29", "GPIO2_4", 11, 0, "X") printer.steppers["Y"] = Stepper_00B1("GPIO1_12", "GPIO0_22", "GPIO2_5", 12, 1, "Y") printer.steppers["Z"] = Stepper_00B1("GPIO0_23", "GPIO0_26", "GPIO0_15", 13, 2, "Z") printer.steppers["E"] = Stepper_00B1("GPIO1_28", "GPIO1_15", "GPIO2_1", 14, 3, "E") printer.steppers["H"] = Stepper_00B1("GPIO1_13", "GPIO1_14", "GPIO2_3", 15, 4, "H") elif self.revision == "00B2": printer.steppers["X"] = Stepper_00B2("GPIO0_27", "GPIO1_29", "GPIO2_4", 11, 0, "X") printer.steppers["Y"] = Stepper_00B2("GPIO1_12", "GPIO0_22", "GPIO2_5", 12, 1, "Y") printer.steppers["Z"] = Stepper_00B2("GPIO0_23", "GPIO0_26", "GPIO0_15", 13, 2, "Z") printer.steppers["E"] = Stepper_00B2("GPIO1_28", "GPIO1_15", "GPIO2_1", 14, 3, "E") printer.steppers["H"] = Stepper_00B2("GPIO1_13", "GPIO1_14", "GPIO2_3", 15, 4, "H") elif self.revision in ["00B3", "0B3A"]: printer.steppers["X"] = Stepper_00B3("GPIO0_27", "GPIO1_29", 90, 11, 0, "X") printer.steppers["Y"] = Stepper_00B3("GPIO1_12", "GPIO0_22", 91, 12, 1, "Y") printer.steppers["Z"] = Stepper_00B3("GPIO0_23", "GPIO0_26", 92, 13, 2, "Z") printer.steppers["E"] = Stepper_00B3("GPIO1_28", "GPIO1_15", 93, 14, 3, "E") printer.steppers["H"] = Stepper_00B3("GPIO1_13", "GPIO1_14", 94, 15, 4, "H") elif self.revision in ["00A4", "0A4A"]: printer.steppers["X"] = Stepper_00A4("GPIO0_27", "GPIO1_29", "GPIO2_4", 0, 0, "X") printer.steppers["Y"] = Stepper_00A4("GPIO1_12", "GPIO0_22", "GPIO2_5", 1, 1, "Y") printer.steppers["Z"] = Stepper_00A4("GPIO0_23", "GPIO0_26", "GPIO0_15", 2, 2, "Z") printer.steppers["E"] = Stepper_00A4("GPIO1_28", "GPIO1_15", "GPIO2_1", 3, 3, "E") printer.steppers["H"] = Stepper_00A4("GPIO1_13", "GPIO1_14", "GPIO2_3", 4, 4, "H") # Init Reach steppers, if present. if printer.config.reach_revision == "00A0": printer.steppers["A"] = Stepper_reach_00A4("GPIO2_2", "GPIO1_18", "GPIO0_14", 5, 5, "A") printer.steppers["B"] = Stepper_reach_00A4("GPIO1_16", "GPIO0_5", "GPIO0_14", 6, 6, "B") printer.steppers["C"] = Stepper_reach_00A4("GPIO0_3", "GPIO3_19", "GPIO0_14", 7, 7, "C") elif printer.config.reach_revision == "00B0": printer.steppers["A"] = Stepper_reach_00B0("GPIO1_16", "GPIO0_5", "GPIO0_3", 5, 5, "A") printer.steppers["B"] = Stepper_reach_00B0("GPIO2_2", "GPIO0_14", "GPIO0_3", 6, 6, "B") # Enable the steppers and set the current, steps pr mm and # microstepping for name, stepper in self.printer.steppers.iteritems(): stepper.in_use = printer.config.getboolean('Steppers', 'in_use_' + name) stepper.direction = printer.config.getint('Steppers', 'direction_' + name) stepper.has_endstop = printer.config.getboolean( 'Endstops', 'has_' + name) stepper.set_current_value( printer.config.getfloat('Steppers', 'current_' + name)) stepper.set_steps_pr_mm( printer.config.getfloat('Steppers', 'steps_pr_mm_' + name)) stepper.set_microstepping( printer.config.getint('Steppers', 'microstepping_' + name)) stepper.set_decay( printer.config.getint("Steppers", "slow_decay_" + name)) # Add soft end stops printer.soft_min[Printer.axis_to_index( name)] = printer.config.getfloat('Endstops', 'soft_end_stop_min_' + name) printer.soft_max[Printer.axis_to_index( name)] = printer.config.getfloat('Endstops', 'soft_end_stop_max_' + name) slave = printer.config.get('Steppers', 'slave_' + name) if slave: printer.add_slave(name, slave) logging.debug("Axis " + name + " has slave " + slave) # Commit changes for the Steppers #Stepper.commit() Stepper.printer = printer # Delta printer setup if printer.axis_config == Printer.AXIS_CONFIG_DELTA: opts = [ "Hez", "L", "r", "Ae", "Be", "Ce", "A_radial", "B_radial", "C_radial", "A_tangential", "B_tangential", "C_tangential" ] for opt in opts: Delta.__dict__[opt] = printer.config.getfloat('Delta', opt) # Discover and add all DS18B20 cold ends. paths = glob.glob("/sys/bus/w1/devices/28-*/w1_slave") logging.debug("Found cold ends: " + str(paths)) for i, path in enumerate(paths): self.printer.cold_ends.append(ColdEnd(path, "ds18b20-" + str(i))) logging.info("Found Cold end " + str(i) + " on " + path) # Make Mosfets, temperature sensors and extruders heaters = ["E", "H", "HBP"] if self.printer.config.reach_revision: heaters.extend(["A", "B", "C"]) for e in heaters: # Mosfets channel = self.printer.config.getint("Heaters", "mosfet_" + e) self.printer.mosfets[e] = Mosfet(channel) # Thermistors adc = self.printer.config.get("Heaters", "path_adc_" + e) if not self.printer.config.has_option("Heaters", "sensor_" + e): sensor = self.printer.config.get("Heaters", "temp_chart_" + e) logging.warning("Deprecated config option temp_chart_" + e + " use sensor_" + e + " instead.") else: sensor = self.printer.config.get("Heaters", "sensor_" + e) self.printer.thermistors[e] = TemperatureSensor( adc, 'MOSFET ' + e, sensor) self.printer.thermistors[e].printer = printer # Extruders onoff = self.printer.config.getboolean('Heaters', 'onoff_' + e) prefix = self.printer.config.get('Heaters', 'prefix_' + e) max_power = self.printer.config.getfloat('Heaters', 'max_power_' + e) if e != "HBP": self.printer.heaters[e] = Extruder(self.printer.steppers[e], self.printer.thermistors[e], self.printer.mosfets[e], e, onoff) else: self.printer.heaters[e] = HBP(self.printer.thermistors[e], self.printer.mosfets[e], onoff) self.printer.heaters[e].prefix = prefix self.printer.heaters[e].Kp = self.printer.config.getfloat( 'Heaters', 'pid_Kp_' + e) self.printer.heaters[e].Ti = self.printer.config.getfloat( 'Heaters', 'pid_Ti_' + e) self.printer.heaters[e].Td = self.printer.config.getfloat( 'Heaters', 'pid_Td_' + e) # Min/max settings self.printer.heaters[e].min_temp = self.printer.config.getfloat( 'Heaters', 'min_temp_' + e) self.printer.heaters[e].max_temp = self.printer.config.getfloat( 'Heaters', 'max_temp_' + e) self.printer.heaters[ e].max_temp_rise = self.printer.config.getfloat( 'Heaters', 'max_rise_temp_' + e) self.printer.heaters[ e].max_temp_fall = self.printer.config.getfloat( 'Heaters', 'max_fall_temp_' + e) # Init the three fans. Argument is PWM channel number self.printer.fans = [] if self.revision == "00A3": self.printer.fans.append(Fan(0)) self.printer.fans.append(Fan(1)) self.printer.fans.append(Fan(2)) elif self.revision == "0A4A": self.printer.fans.append(Fan(8)) self.printer.fans.append(Fan(9)) self.printer.fans.append(Fan(10)) elif self.revision in ["00B1", "00B2", "00B3", "0B3A"]: self.printer.fans.append(Fan(7)) self.printer.fans.append(Fan(8)) self.printer.fans.append(Fan(9)) self.printer.fans.append(Fan(10)) if printer.config.reach_revision == "00A0": self.printer.fans.append(Fan(14)) self.printer.fans.append(Fan(15)) self.printer.fans.append(Fan(7)) # Set default value for all fans for i, f in enumerate(self.printer.fans): f.set_value( self.printer.config.getfloat('Fans', "default-fan-{}-value".format(i))) # Init the servos printer.servos = [] servo_nr = 0 while (printer.config.has_option("Servos", "servo_" + str(servo_nr) + "_enable")): if printer.config.getboolean("Servos", "servo_" + str(servo_nr) + "_enable"): channel = printer.config.get( "Servos", "servo_" + str(servo_nr) + "_channel") pulse_min = printer.config.getfloat( "Servos", "servo_" + str(servo_nr) + "_pulse_min") pulse_max = printer.config.getfloat( "Servos", "servo_" + str(servo_nr) + "_pulse_max") angle_min = printer.config.getfloat( "Servos", "servo_" + str(servo_nr) + "_angle_min") angle_max = printer.config.getfloat( "Servos", "servo_" + str(servo_nr) + "_angle_max") angle_init = printer.config.getfloat( "Servos", "servo_" + str(servo_nr) + "_angle_init") s = Servo(channel, pulse_min, pulse_max, angle_min, angle_max, angle_init) printer.servos.append(s) logging.info("Added servo " + str(servo_nr)) servo_nr += 1 # Connect thermitors to fans for t, therm in self.printer.heaters.iteritems(): for f, fan in enumerate(self.printer.fans): if not self.printer.config.has_option( 'Cold-ends', "connect-therm-{}-fan-{}".format(t, f)): continue if printer.config.getboolean( 'Cold-ends', "connect-therm-{}-fan-{}".format(t, f)): c = Cooler(therm, fan, "Cooler-{}-{}".format(t, f), True) # Use ON/OFF on these. c.ok_range = 4 opt_temp = "therm-{}-fan-{}-target_temp".format(t, f) if printer.config.has_option('Cold-ends', opt_temp): target_temp = printer.config.getfloat( 'Cold-ends', opt_temp) else: target_temp = 60 c.set_target_temperature(target_temp) c.enable() printer.coolers.append(c) logging.info("Cooler connects therm {} with fan {}".format( t, f)) # Connect fans to M106 printer.controlled_fans = [] for i, fan in enumerate(self.printer.fans): if not self.printer.config.has_option( 'Cold-ends', "add-fan-{}-to-M106".format(i)): continue if self.printer.config.getboolean('Cold-ends', "add-fan-{}-to-M106".format(i)): printer.controlled_fans.append(self.printer.fans[i]) logging.info("Added fan {} to M106/M107".format(i)) # Connect the colds to fans for ce, cold_end in enumerate(self.printer.cold_ends): for f, fan in enumerate(self.printer.fans): option = "connect-ds18b20-{}-fan-{}".format(ce, f) if self.printer.config.has_option('Cold-ends', option): if self.printer.config.getboolean('Cold-ends', option): c = Cooler(cold_end, fan, "Cooler-ds18b20-{}-{}".format(ce, f), False) c.ok_range = 4 opt_temp = "cooler_{}_target_temp".format(ce) if printer.config.has_option('Cold-ends', opt_temp): target_temp = printer.config.getfloat( 'Cold-ends', opt_temp) else: target_temp = 60 c.set_target_temperature(target_temp) c.enable() printer.coolers.append(c) logging.info( "Cooler connects temp sensor ds18b20 {} with fan {}" .format(ce, f)) # Init roatray encs. printer.filament_sensors = [] # Init rotary encoders printer.rotary_encoders = [] for ex in ["E", "H", "A", "B", "C"]: if not printer.config.has_option('Rotary-encoders', "enable-{}".format(ex)): continue if printer.config.getboolean("Rotary-encoders", "enable-{}".format(ex)): logging.debug("Rotary encoder {} enabled".format(ex)) event = printer.config.get("Rotary-encoders", "event-{}".format(ex)) cpr = printer.config.getint("Rotary-encoders", "cpr-{}".format(ex)) diameter = printer.config.getfloat("Rotary-encoders", "diameter-{}".format(ex)) r = RotaryEncoder(event, cpr, diameter) printer.rotary_encoders.append(r) # Append as Filament Sensor ext_nr = Printer.axis_to_index(ex) - 3 sensor = FilamentSensor(ex, r, ext_nr, printer) alarm_level = printer.config.getfloat( "Filament-sensors", "alarm-level-{}".format(ex)) logging.debug("Alarm level" + str(alarm_level)) sensor.alarm_level = alarm_level printer.filament_sensors.append(sensor) # Make a queue of commands self.printer.commands = JoinableQueue(10) # Make a queue of commands that should not be buffered self.printer.sync_commands = JoinableQueue() self.printer.unbuffered_commands = JoinableQueue(10) # Bed compensation matrix printer.matrix_bed_comp = printer.load_bed_compensation_matrix() logging.debug("Loaded bed compensation matrix: \n" + str(printer.matrix_bed_comp)) for axis in printer.steppers.keys(): i = Printer.axis_to_index(axis) printer.max_speeds[i] = printer.config.getfloat( 'Planner', 'max_speed_' + axis.lower()) printer.min_speeds[i] = printer.config.getfloat( 'Planner', 'min_speed_' + axis.lower()) printer.jerks[i] = printer.config.getfloat( 'Planner', 'max_jerk_' + axis.lower()) printer.home_speed[i] = printer.config.getfloat( 'Homing', 'home_speed_' + axis.lower()) printer.home_backoff_speed[i] = printer.config.getfloat( 'Homing', 'home_backoff_speed_' + axis.lower()) printer.home_backoff_offset[i] = printer.config.getfloat( 'Homing', 'home_backoff_offset_' + axis.lower()) printer.steps_pr_meter[i] = printer.steppers[ axis].get_steps_pr_meter() printer.backlash_compensation[i] = printer.config.getfloat( 'Steppers', 'backlash_' + axis.lower()) printer.e_axis_active = printer.config.getboolean( 'Planner', 'e_axis_active') dirname = os.path.dirname(os.path.realpath(__file__)) # Create the firmware compiler pru_firmware = PruFirmware(dirname + "/firmware/firmware_runtime.p", dirname + "/firmware/firmware_runtime.bin", dirname + "/firmware/firmware_endstops.p", dirname + "/firmware/firmware_endstops.bin", self.printer, "/usr/bin/pasm") printer.move_cache_size = printer.config.getfloat( 'Planner', 'move_cache_size') printer.print_move_buffer_wait = printer.config.getfloat( 'Planner', 'print_move_buffer_wait') printer.min_buffered_move_time = printer.config.getfloat( 'Planner', 'min_buffered_move_time') printer.max_buffered_move_time = printer.config.getfloat( 'Planner', 'max_buffered_move_time') printer.max_length = printer.config.getfloat('Planner', 'max_length') self.printer.processor = GCodeProcessor(self.printer) self.printer.plugins = PluginsController(self.printer) # Path planner travel_default = False center_default = False home_default = False # Setting acceleration before PathPlanner init for axis in printer.steppers.keys(): printer.acceleration[Printer.axis_to_index( axis)] = printer.config.getfloat( 'Planner', 'acceleration_' + axis.lower()) self.printer.path_planner = PathPlanner(self.printer, pru_firmware) for axis in printer.steppers.keys(): i = Printer.axis_to_index(axis) # Sometimes soft_end_stop aren't defined to be at the exact hardware boundary. # Adding 100mm for searching buffer. if printer.config.has_option('Geometry', 'travel_' + axis.lower()): printer.path_planner.travel_length[ axis] = printer.config.getfloat('Geometry', 'travel_' + axis.lower()) else: printer.path_planner.travel_length[axis] = ( printer.soft_max[i] - printer.soft_min[i]) + .1 if axis in ['X', 'Y', 'Z']: travel_default = True if printer.config.has_option('Geometry', 'offset_' + axis.lower()): printer.path_planner.center_offset[ axis] = printer.config.getfloat('Geometry', 'offset_' + axis.lower()) else: printer.path_planner.center_offset[axis] = ( printer.soft_min[i] if printer.home_speed[i] > 0 else printer.soft_max[i]) if axis in ['X', 'Y', 'Z']: center_default = True if printer.config.has_option('Homing', 'home_' + axis.lower()): printer.path_planner.home_pos[axis] = printer.config.getfloat( 'Homing', 'home_' + axis.lower()) else: printer.path_planner.home_pos[ axis] = printer.path_planner.center_offset[axis] if axis in ['X', 'Y', 'Z']: home_default = True if printer.axis_config == Printer.AXIS_CONFIG_DELTA: if travel_default: logging.warning( "Axis travel (travel_*) set by soft limits, manual setup is recommended for a delta" ) if center_default: logging.warning( "Axis offsets (offset_*) set by soft limits, manual setup is recommended for a delta" ) if home_default: logging.warning( "Home position (home_*) set by soft limits or offset_*") logging.info("Home position will be recalculated...") # convert home_pos to effector space Az = printer.path_planner.home_pos['X'] Bz = printer.path_planner.home_pos['Y'] Cz = printer.path_planner.home_pos['Z'] delta_bot = self.printer.path_planner.native_planner.delta_bot z_offset = delta_bot.vertical_offset(Az, Bz, Cz) # vertical offset xyz = delta_bot.forward_kinematics(Az, Bz, Cz) # effector position # The default home_pos, provided above, is based on effector space # coordinates for carriage positions. We need to transform these to # get where the effector actually is. xyz[2] += z_offset for i, a in enumerate(['X', 'Y', 'Z']): printer.path_planner.home_pos[a] = xyz[i] logging.info("Home position = %s" % str(printer.path_planner.home_pos)) # Read end stop value again now that PRU is running for _, es in self.printer.end_stops.iteritems(): es.read_value() # Enable Stepper timeout timeout = printer.config.getint('Steppers', 'timeout_seconds') printer.swd = StepperWatchdog(printer, timeout) if printer.config.getboolean('Steppers', 'use_timeout'): printer.swd.start() # Set up communication channels printer.comms["USB"] = USB(self.printer) printer.comms["Eth"] = Ethernet(self.printer) if Pipe.check_tty0tty() or Pipe.check_socat(): printer.comms["octoprint"] = Pipe(printer, "octoprint") printer.comms["toggle"] = Pipe(printer, "toggle") printer.comms["testing"] = Pipe(printer, "testing") printer.comms["testing_noret"] = Pipe(printer, "testing_noret") # Does not send "ok" printer.comms["testing_noret"].send_response = False else: logging.warning( "Neither tty0tty or socat is installed! No virtual tty pipes enabled" )
def make_config_file(self): # Create a config file configFile_0 = os.path.join("/tmp", 'config.h') with open(configFile_0, 'w') as configFile: # GPIO banks banks = {"0": 0, "1": 0, "2": 0, "3": 0} step_banks = {"0": 0, "1": 0, "2": 0, "3": 0} dir_banks = {"0": 0, "1": 0, "2": 0, "3": 0} direction_mask = 0 # Define step and dir pins for name, stepper in iteritems(self.printer.steppers): step_pin = str(stepper.get_step_pin()) step_bank = str(stepper.get_step_bank()) dir_pin = str(stepper.get_dir_pin()) dir_bank = str(stepper.get_dir_bank()) configFile.write('#define STEPPER_' + name + '_STEP_BANK\t\t' + "STEPPER_GPIO_" + step_bank + '\n') configFile.write('#define STEPPER_' + name + '_STEP_PIN\t\t' + step_pin + '\n') configFile.write('#define STEPPER_' + name + '_DIR_BANK\t\t' + "STEPPER_GPIO_" + dir_bank + '\n') configFile.write('#define STEPPER_' + name + '_DIR_PIN\t\t' + dir_pin + '\n') # Define direction direction = "0" if self.config.getint( 'Steppers', 'direction_' + name) > 0 else "1" configFile.write('#define STEPPER_' + name + '_DIRECTION\t\t' + direction + '\n') index = Printer.axis_to_index(name) direction_mask |= (int(direction) << index) # Generate the GPIO bank masks banks[step_bank] |= (1 << int(step_pin)) banks[dir_bank] |= (1 << int(dir_pin)) step_banks[step_bank] |= (1 << int(step_pin)) dir_banks[dir_bank] |= (1 << int(dir_pin)) configFile.write('#define DIRECTION_MASK ' + bin(direction_mask) + '\n') configFile.write('\n') # Define end stop pins and banks for name, endstop in iteritems(self.printer.end_stops): bank, pin = endstop.get_gpio_bank_and_pin() configFile.write('#define STEPPER_' + name + '_END_PIN\t\t' + str(pin) + '\n') configFile.write('#define STEPPER_' + name + '_END_BANK\t\t' + "GPIO_" + str(bank) + '_IN\n') configFile.write('\n') # Construct the end stop inversion mask inversion_mask = "#define INVERSION_MASK\t\t0b00" for name in ["Z2", "Y2", "X2", "Z1", "Y1", "X1"]: inversion_mask += "1" if self.config.getboolean( 'Endstops', 'invert_' + name) else "0" configFile.write(inversion_mask + "\n") # Construct the endstop lookup table. for name, endstop in iteritems(self.printer.end_stops): mask = 0 # stepper name is x_cw or x_ccw option = 'end_stop_' + name + '_stops' for stepper in self.config.get('Endstops', option).split(","): stepper = stepper.strip().strip('"') if stepper == "": continue m = re.search('^([xyzehabc])_(ccw|cw|pos|neg)$', stepper) if (m == None): raise RuntimeError("'" + stepper + "' is invalid for " + option) # direction should be 1 for normal operation and -1 to invert the stepper. if (m.group(2) == "pos"): direction = -1 elif (m.group(2) == "neg"): direction = 1 else: direction = 1 if self.config.getint( 'Steppers', 'direction_' + stepper[0]) > 0 else -1 if (m.group(2) == "ccw"): direction *= -1 cur = 1 << ("xyzehabc".index(m.group(1))) if (direction == -1): cur <<= 8 mask += cur logging.debug("Endstop {0} mask = {1}".format(name, bin(mask))) bin_mask = "0b" + (bin(mask)[2:]).zfill(16) configFile.write("#define STEPPER_MASK_" + name + "\t\t" + bin_mask + "\n") configFile.write("\n") # Put each dir and step pin in the proper bank if they are for GPIO0 or GPIO1 bank. # This is a restriction due to the limited capabilities of the pasm preprocessor. for name, bank in iteritems(banks): #bank = (~bank & 0xFFFFFFFF) configFile.write("#define GPIO" + name + "_MASK\t\t" + bin(bank) + "\n") # for name, bank in iteritems(step_banks): #bank = (~bank & 0xFFFFFFFF) # configFile.write("#define GPIO"+name+"_STEP_MASK\t\t" +bin(bank)+ "\n"); for name, bank in iteritems(dir_banks): #bank = (~bank & 0xFFFFFFFF) configFile.write("#define GPIO" + name + "_DIR_MASK\t\t" + bin(bank) + "\n") configFile.write("\n") # Add end stop delay to the config file end_stop_delay = self.config.getint('Endstops', 'end_stop_delay_cycles') configFile.write("#define END_STOP_DELAY " + str(end_stop_delay) + "\n") revision = self.printer.config.replicape_revision.strip('0') # Note that these are all cycle counts of the 200MHz PRU - 1 cycle is 5ns if revision.startswith('A'): # DRV8825 configFile.write("#define DELAY_BETWEEN_DIR_AND_STEP 130\n" ) # t_SU in the spec sheet configFile.write("#define DELAY_BETWEEN_STEP_AND_CLEAR 380\n" ) # t_WH in the spec sheet configFile.write("#define MINIMUM_DELAY_AFTER_STEP 380\n" ) # t_WL in the spec sheet elif revision.startswith('B'): # TMC2100 configFile.write("#define DELAY_BETWEEN_DIR_AND_STEP 4\n" ) # t_DSU in the spec sheet configFile.write( "#define DELAY_BETWEEN_STEP_AND_CLEAR 20\n" ) # t_SH in the spec sheet - assume internal clock of 14MHz, which means we need max(~85, t_clk+20). t_clk+20 is ~91.43, which we round up for safety configFile.write("#define MINIMUM_DELAY_AFTER_STEP 24\n" ) # t_SL with t_DSH added for safety else: raise RuntimeError("Unknown Replicape revision " + revision + ", cannot determine stepper delays") return configFile_0