From ba62242dffeb2c6d008cd4510aae2240785188ea Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Thu, 12 Oct 2017 20:22:44 -0400 Subject: [PATCH 01/14] basic structure for redeem config data structure --- redeem/configuration/RedeemConfig.py | 43 ++++++++++ redeem/configuration/__init__.py | 0 redeem/configuration/sections/__init__.py | 23 ++++++ redeem/configuration/sections/coldends.py | 52 +++++++++++++ redeem/configuration/sections/delta.py | 15 ++++ redeem/configuration/sections/endstops.py | 90 +++++++++++++++++++++ redeem/configuration/sections/fans.py | 21 +++++ redeem/configuration/sections/geometry.py | 17 ++++ redeem/configuration/sections/heaters.py | 95 +++++++++++++++++++++++ redeem/configuration/sections/planner.py | 30 +++++++ redeem/configuration/sections/stepper.py | 49 ++++++++++++ redeem/configuration/sections/system.py | 11 +++ 12 files changed, 446 insertions(+) create mode 100644 redeem/configuration/RedeemConfig.py create mode 100644 redeem/configuration/__init__.py create mode 100644 redeem/configuration/sections/__init__.py create mode 100644 redeem/configuration/sections/coldends.py create mode 100644 redeem/configuration/sections/delta.py create mode 100644 redeem/configuration/sections/endstops.py create mode 100644 redeem/configuration/sections/fans.py create mode 100644 redeem/configuration/sections/geometry.py create mode 100644 redeem/configuration/sections/heaters.py create mode 100644 redeem/configuration/sections/planner.py create mode 100644 redeem/configuration/sections/stepper.py create mode 100644 redeem/configuration/sections/system.py diff --git a/redeem/configuration/RedeemConfig.py b/redeem/configuration/RedeemConfig.py new file mode 100644 index 00000000..09e1269f --- /dev/null +++ b/redeem/configuration/RedeemConfig.py @@ -0,0 +1,43 @@ +from redeem.configuration.sections.coldends import ColdendsConfig +from redeem.configuration.sections.delta import DeltaConfig +from redeem.configuration.sections.endstops import EndstopsConfig +from redeem.configuration.sections.fans import FansConfig +from redeem.configuration.sections.geometry import GeometryConfig +from redeem.configuration.sections.heaters import HeatersConfig +from redeem.configuration.sections.planner import PlannerConfig +from redeem.configuration.sections.stepper import StepperConfig +from redeem.configuration.sections.system import SystemConfig + + +class RedeemConfig(object): + """match the 'interface' for the ConfigParser to minimize the need + to make changes where `printer.config` is accessed in the code base. + """ + + system = SystemConfig() + + + + stepper = StepperConfig() + planner = PlannerConfig() + # system = SystemConfig + # .... + + def get(self, section, key): + if hasattr(self, section.lower()): + return getattr(self, section.lower()).get(key) + return None + + def getint(self, section, key): + if hasattr(self, section.lower()): + return getattr(self, section.lower()).getint(key) + return None + + def getfloat(self, section, key): + if hasattr(self, section.lower()): + return getattr(self, section.lower()).getfloat(key) + return None + + # . + # . + # . \ No newline at end of file diff --git a/redeem/configuration/__init__.py b/redeem/configuration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/redeem/configuration/sections/__init__.py b/redeem/configuration/sections/__init__.py new file mode 100644 index 00000000..2fdabf94 --- /dev/null +++ b/redeem/configuration/sections/__init__.py @@ -0,0 +1,23 @@ +class BaseConfig(object): + """Superclass of all config 'sections'""" + + def get(self, key): + if not getattr(self, key, None): + return None + return getattr(self, key) + + def getfloat(self, key): + val = self.get(key) + try: + val = float(val) + except ValueError: + return None + return val + + def getint(self, key): + val = self.get(key) + try: + val = int(val) + except ValueError: + return None + return val diff --git a/redeem/configuration/sections/coldends.py b/redeem/configuration/sections/coldends.py new file mode 100644 index 00000000..9101f086 --- /dev/null +++ b/redeem/configuration/sections/coldends.py @@ -0,0 +1,52 @@ +from redeem.configuration.sections import BaseConfig + + +class ColdendsConfig(BaseConfig): + # To use the DS18B20 temp sensors, connect them like this, enable by setting to True + connect_ds18b20_0_fan_0 = False + connect_ds18b20_1_fan_0 = False + connect_ds18b20_0_fan_1 = False + + # This list is for connecting thermistors to fans, so they are controlled automatically when reaching 60 degrees. + connect_therm_E_fan_0 = False + connect_therm_E_fan_1 = False + connect_therm_E_fan_2 = False + connect_therm_E_fan_3 = False + connect_therm_H_fan_0 = False + connect_therm_H_fan_1 = False + connect_therm_H_fan_2 = False + connect_therm_H_fan_3 = False + connect_therm_A_fan_0 = False + connect_therm_A_fan_1 = False + connect_therm_A_fan_2 = False + connect_therm_A_fan_3 = False + connect_therm_B_fan_0 = False + connect_therm_B_fan_1 = False + connect_therm_B_fan_2 = False + connect_therm_B_fan_3 = False + connect_therm_C_fan_0 = False + connect_therm_C_fan_1 = False + connect_therm_C_fan_2 = False + connect_therm_C_fan_3 = False + connect_therm_HBP_fan_0 = False + connect_therm_HBP_fan_1 = False + connect_therm_HBP_fan_2 = False + connect_therm_HBP_fan_3 = False + + add_fan_0_to_M106 = False + add_fan_1_to_M106 = False + add_fan_2_to_M106 = False + add_fan_3_to_M106 = False + + cooler_0_target_temp = 60 # If you want coolers to have a different 'keep' temp, list it here. + + # If you want the fan-thermitor connetions to have a + # different temperature: + # therm-e-fan-0-target_temp = 70 + + def get(self, key): + key = key.replace('_', '-') + + if not getattr(self, key, None): + return None + return getattr(self, key) diff --git a/redeem/configuration/sections/delta.py b/redeem/configuration/sections/delta.py new file mode 100644 index 00000000..1737a6d2 --- /dev/null +++ b/redeem/configuration/sections/delta.py @@ -0,0 +1,15 @@ +from redeem.configuration.sections import BaseConfig + + +class DeltaConfig(BaseConfig): + + L = 0.135 # Length of the rod (m) + r = 0.144 # Radius of the columns (m) + + # Compensation for positional error of the columns + # Radial offsets of the columns, positive values move the tower away from the center of the printer (m) + A_radial, B_radial, C_radial = 0.0, 0.0, 0.0 + + # Angular offsets of the columns + # Positive values move the tower counter-clockwise, as seen from above (degrees) + A_angular, B_angular, C_angular = 0.0, 0.0, 0.0 diff --git a/redeem/configuration/sections/endstops.py b/redeem/configuration/sections/endstops.py new file mode 100644 index 00000000..f39cc394 --- /dev/null +++ b/redeem/configuration/sections/endstops.py @@ -0,0 +1,90 @@ +from redeem.configuration.sections import BaseConfig + + +class EndstopsConfig(BaseConfig): + + # Which axis should be homed. + has_x = True + has_y = True + has_z = True + has_e = False + has_h = False + has_a = False + has_b = False + has_c = False + + inputdev = '/dev/input/by-path/platform-ocp:gpio_keys-event' + + # Number of cycles to wait between checking + # end stops. CPU frequency is 200 MHz + end_stop_delay_cycles = 1000 + + # Invert = + # True means endstop is connected as Normally Open (NO) or not connected + # False means endstop is connected as Normally Closed (NC) + invert_X1 = False + invert_X2 = False + invert_Y1 = False + invert_Y2 = False + invert_Z1 = False + invert_Z2 = False + + pin_X1 = 'GPIO3_21' + pin_X2 = 'GPIO0_30' + pin_Y1 = 'GPIO1_17' + pin_Y2 = 'GPIO3_17' + pin_Z1 = 'GPIO0_31' + pin_Z2 = 'GPIO0_4' + + keycode_X1 = 112 + keycode_X2 = 113 + keycode_Y1 = 114 + keycode_Y2 = 115 + keycode_Z1 = 116 + keycode_Z2 = 117 + + # If one endstop is hit, which steppers and directions are masked. + # The list is comma separated and has format + # x_cw = stepper x clockwise (independent of direction_x) + # x_ccw = stepper x counter clockwise (independent of direction_x) + # x_neg = setpper x negative direction (affected by direction_x) + # x_pos = setpper x positive direction (affected by direction_x) + # Steppers e and h (and a, b, c for reach) can also be masked. + # + # For a list of steppers to stop, use this format: x_cw, y_ccw + # For Simple XYZ bot, the usual practice would be + # end_stop_X1_stops = x_neg, end_stop_X2_stops = x_pos, ... + # For CoreXY and similar, two steppers should be stopped if an end stop is hit. + # similarly for a delta probe should stop x, y and z. + end_stop_X1_stops = None + end_stop_Y1_stops = None + end_stop_Z1_stops = None + end_stop_X2_stops = None + end_stop_Y2_stops = None + end_stop_Z2_stops = None + + # if an endstop should only be used for homing or probing, then add it to + # homing_only_endstops in comma separated format. + # Example: homing_only_endstops = Z1, Z2 + # this will make sure that endstop Z1 and Z2 are only used during homing or probing + # NOTE: Be very careful with this option. + + homing_only_endstops = None + + soft_end_stop_min_x = -0.5 + soft_end_stop_min_y = -0.5 + soft_end_stop_min_z = -0.5 + soft_end_stop_min_e = -1000.0 + soft_end_stop_min_h = -1000.0 + soft_end_stop_min_a = -1000.0 + soft_end_stop_min_b = -1000.0 + soft_end_stop_min_c = -1000.0 + + soft_end_stop_max_x = 0.5 + soft_end_stop_max_y = 0.5 + soft_end_stop_max_z = 0.5 + soft_end_stop_max_e = 1000.0 + soft_end_stop_max_h = 1000.0 + soft_end_stop_max_a = 1000.0 + soft_end_stop_max_b = 1000.0 + soft_end_stop_max_c = 1000.0 \ No newline at end of file diff --git a/redeem/configuration/sections/fans.py b/redeem/configuration/sections/fans.py new file mode 100644 index 00000000..3cc133cb --- /dev/null +++ b/redeem/configuration/sections/fans.py @@ -0,0 +1,21 @@ +from redeem.configuration.sections import BaseConfig + + +class FansConfig(BaseConfig): + default_fan_0_value = 0.0 + default_fan_1_value = 0.0 + default_fan_2_value = 0.0 + default_fan_3_value = 0.0 + default_fan_4_value = 0.0 + default_fan_5_value = 0.0 + default_fan_6_value = 0.0 + default_fan_7_value = 0.0 + default_fan_8_value = 0.0 + default_fan_9_value = 0.0 + + def get(self, key): + key = key.replace('_', '-') + + if not getattr(self, key, None): + return None + return getattr(self, key) diff --git a/redeem/configuration/sections/geometry.py b/redeem/configuration/sections/geometry.py new file mode 100644 index 00000000..7533f03c --- /dev/null +++ b/redeem/configuration/sections/geometry.py @@ -0,0 +1,17 @@ +from redeem.configuration.sections import BaseConfig + + +class GeometryConfig(BaseConfig): + axis_config = 0 # 0 - Cartesian, 1 - H-belt, 2 - Core XY, 3 - Delta + + # The total length each axis can travel, this affects the homing endstop searching length. + travel_x, travel_y, travel_z = 0.2, 0.2, 0.2 + travel_e, travel_h = 0.2, 0.2 + travel_a, travel_b, travel_c = 0.0, 0.0, 0.0 + + # Define the origin of the build plate in relation to the endstops + offset_x, offset_y, offset_z = 0.0, 0.0, 0.0 + offset_e, offset_h = 0.0, 0.0 + offset_a, offset_b, offset_c = 0.0, 0.0, 0.0 + + bed_compensation_matrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] diff --git a/redeem/configuration/sections/heaters.py b/redeem/configuration/sections/heaters.py new file mode 100644 index 00000000..81ad74e7 --- /dev/null +++ b/redeem/configuration/sections/heaters.py @@ -0,0 +1,95 @@ +from redeem.configuration.sections import BaseConfig + + +class HeatersConfig(BaseConfig): + # For list of available temp charts, look in temp_chart.py + + sensor_E = 'B57560G104F' + pid_Kp_E = 0.1 + pid_Ti_E = 100.0 + pid_Td_E = 0.3 + ok_range_E = 4.0 + max_rise_temp_E = 10.0 + max_fall_temp_E = 10.0 + min_temp_E = 20.0 + max_temp_E = 250.0 + path_adc_E = '/sys/bus/iio/devices/iio:device0/in_voltage4_raw' + mosfet_E = 5 + onoff_E = False + prefix_E = 'T0' + max_power_E = 1.0 + + sensor_H = 'B57560G104F' + pid_Kp_H = 0.1 + pid_Ti_H = 0.01 + pid_Td_H = 0.3 + ok_range_H = 4.0 + max_rise_temp_H = 10.0 + max_fall_temp_H = 10.0 + min_temp_H = 20.0 + max_temp_H = 250.0 + path_adc_H = '/sys/bus/iio/devices/iio:device0/in_voltage5_raw' + mosfet_H = 3 + onoff_H = False + prefix_H = 'T1' + max_power_H = 1.0 + + sensor_A = 'B57560G104F' + pid_Kp_A = 0.1 + pid_Ti_A = 0.01 + pid_Td_A = 0.3 + ok_range_A = 4.0 + max_rise_temp_A = 10.0 + max_fall_temp_A = 10.0 + min_temp_A = 20.0 + max_temp_A = 250.0 + path_adc_A = '/sys/bus/iio/devices/iio:device0/in_voltage0_raw' + mosfet_A = 11 + onoff_A = False + prefix_A = 'T2' + max_power_A = 1.0 + + sensor_B = 'B57560G104F' + pid_Kp_B = 0.1 + pid_Ti_B = 0.01 + pid_Td_B = 0.3 + ok_range_B = 4.0 + max_rise_temp_B = 10.0 + max_fall_temp_B = 10.0 + min_temp_B = 20.0 + max_temp_B = 250.0 + path_adc_B = '/sys/bus/iio/devices/iio:device0/in_voltage3_raw' + mosfet_B = 12 + onoff_B = False + prefix_B = 'T3' + max_power_B = 1.0 + + sensor_C = 'B57560G104F' + pid_Kp_C = 0.1 + pid_Ti_C = 0.01 + pid_Td_C = 0.3 + ok_range_C = 4.0 + max_rise_temp_C = 10.0 + max_fall_temp_C = 10.0 + min_temp_C = 20.0 + max_temp_C = 250.0 + path_adc_C = '/sys/bus/iio/devices/iio:device0/in_voltage2_raw' + mosfet_C = 13 + onoff_C = False + prefix_C = 'T4' + max_power_C = 1.0 + + sensor_HBP = 'B57560G104F' + pid_Kp_HBP = 0.1 + pid_Ti_HBP = 0.01 + pid_Td_HBP = 0.3 + ok_range_HBP = 4.0 + max_rise_temp_HBP = 10.0 + max_fall_temp_HBP = 10.0 + min_temp_HBP = 20.0 + max_temp_HBP = 250.0 + path_adc_HBP = '/sys/bus/iio/devices/iio:device0/in_voltage6_raw' + mosfet_HBP = 4 + onoff_HBP = False + prefix_HBP = 'B' + max_power_HBP = 1.0 diff --git a/redeem/configuration/sections/planner.py b/redeem/configuration/sections/planner.py new file mode 100644 index 00000000..8d6237f3 --- /dev/null +++ b/redeem/configuration/sections/planner.py @@ -0,0 +1,30 @@ +from redeem.configuration.sections import BaseConfig + + +class PlannerConfig(BaseConfig): + + move_cache_size = 1024 # size of the path planning cache + print_move_buffer_wait = 250 # time to wait for buffer to fill, (ms) + + # total buffered move time should not exceed this much (ms) + max_buffered_move_time = 1000 + + acceleration_x, acceleration_y, acceleration_z = 0.5, 0.5, 0.5 + acceleration_e, acceleration_h = 0.5, 0.5 + acceleration_a, acceleration_b, acceleration_c = 0.5, 0.5, 0.5 + + max_jerk_x, max_jerk_y, max_jerk_z = 0.01, 0.01, 0.01 + max_jerk_e, max_jerk_h = 0.01, 0.01 + max_jerk_a, max_jerk_b, max_jerk_c = 0.01, 0.01, 0.01 + + # Max speed for the steppers in m/s + max_speed_x, max_speed_y, max_speed_z = 0.2, 0.2, 0.02 + max_speed_e, max_speed_h = 0.2, 0.2 + max_speed_a, max_speed_b, max_speed_c = 0.2, 0.2, 0.2 + + arc_segment_length = 0.001 # for arc commands, seperate into segments of length in m + + # When true, movements on the E axis (eg, G1, G92) will apply + # to the active tool (similar to other firmwares). When false, + # such movements will only apply to the E axis. + e_axis_active = True diff --git a/redeem/configuration/sections/stepper.py b/redeem/configuration/sections/stepper.py new file mode 100644 index 00000000..1f300ebc --- /dev/null +++ b/redeem/configuration/sections/stepper.py @@ -0,0 +1,49 @@ +from redeem.configuration.sections import BaseConfig + + +class StepperConfig(BaseConfig): + + microstepping_x, microstepping_y, microstepping_z = 3, 3, 3 + microstepping_e, microstepping_h = 3, 3 + microstepping_a, microstepping_b, microstepping_c = 3, 3, 3 + + current_x, current_y, current_z = 0.5, 0.5, 0.5 + current_e, current_h = 0.5, 0.5 + current_a, current_b, current_c = 0.5, 0.5, 0.5 + + # full steps per 1 mm, ignore microstepping settings + steps_pr_mm_x, steps_pr_mm_y, steps_pr_mm_z = 4.0, 4.0, 50.0 + steps_pr_mm_e, steps_pr_mm_h = 6.0, 6.0 + steps_pr_mm_a, steps_pr_mm_b, steps_pr_mm_c = 6.0, 6.0, 6.0 + + backlash_x, backlash_y, backlash_z = 0.0, 0.0, 0.0 + backlash_e, backlash_h = 0.0, 0.0 + backlash_a, backlash_b, backlash_c = 0.0, 0.0, 0.0 + + # Which steppers are enabled + in_use_x, in_use_y, in_use_z = True, True, True + in_use_e, in_use_h = True, True + in_use_a, in_use_b, in_use_c = False, False, False + + # Set to -1 if axis is inverted + direction_x, direction_y, direction_z = 1, 1, 1 + direction_e, direction_h = 1, 1 + direction_a, direction_b, direction_c = 1, 1, 1 + + # Set to True if slow decay mode is needed + slow_decay_x, slow_decay_y, slow_decay_z = 0, 0, 0 + slow_decay_e, slow_decay_h = 0, 0 + slow_decay_a, slow_decay_b, slow_decay_c = 0, 0, 0 + + # A stepper controller can operate in slave mode, + # meaning that it will mirror the position of the + # specified stepper. Typically, H will mirror Y or Z, + # in the case of the former, write this: slave_y = H. + slave_x, slave_y, slave_z = None, None, None + slave_e, slave_h = None, None + slave_a, slave_b, slave_c = None, None, None + + # Stepper timout + use_timeout = True + timeout_seconds = 500 + diff --git a/redeem/configuration/sections/system.py b/redeem/configuration/sections/system.py new file mode 100644 index 00000000..5babadfe --- /dev/null +++ b/redeem/configuration/sections/system.py @@ -0,0 +1,11 @@ +from redeem.configuration.sections import BaseConfig + + +class SystemConfig(BaseConfig): + + loglevel = 20 # CRITICAL=50, # ERROR=40, # WARNING=30, INFO=20, DEBUG=10, NOTSET=0 + log_to_file = True + logfile = '/home/octo/.octoprint/logs/plugin_redeem.log' + data_path = "/etc/redeem" # location to look for data files (temperature charts, etc) + plugins = [] # Plugin to load for redeem, comma separated (i.e. HPX2Max,plugin2,plugin3) + machine_type = None # Machine type is used by M115 to identify the machine connected. From ee6d00135861112294052333affbd12315fadf89 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Wed, 25 Oct 2017 22:52:58 -0400 Subject: [PATCH 02/14] test and verify that redeem config matches default --- redeem/configuration/RedeemConfig.py | 55 +- .../configuration/factories/ConfigFactory.py | 70 ++ .../factories/ConfigFactoryV19.py | 5 + redeem/configuration/factories/__init__.py | 0 redeem/configuration/sections/__init__.py | 16 +- redeem/configuration/sections/alarms.py | 5 + redeem/configuration/sections/coldends.py | 67 +- redeem/configuration/sections/delta.py | 6 +- redeem/configuration/sections/endstops.py | 52 +- redeem/configuration/sections/fans.py | 7 - .../configuration/sections/filamentsensors.py | 7 + redeem/configuration/sections/heaters.py | 168 ++--- redeem/configuration/sections/homing.py | 53 ++ redeem/configuration/sections/macros.py | 46 ++ redeem/configuration/sections/plugins.py | 30 + redeem/configuration/sections/probe.py | 11 + .../configuration/sections/rotaryencoders.py | 8 + redeem/configuration/sections/servos.py | 19 + .../sections/{stepper.py => steppers.py} | 8 +- redeem/configuration/sections/system.py | 4 +- redeem/configuration/sections/watchdog.py | 5 + tests/core/resources/default.2.0.cfg | 616 ++++++++++++++++++ tests/core/test_configs.py | 30 + 23 files changed, 1103 insertions(+), 185 deletions(-) create mode 100644 redeem/configuration/factories/ConfigFactory.py create mode 100644 redeem/configuration/factories/ConfigFactoryV19.py create mode 100644 redeem/configuration/factories/__init__.py create mode 100644 redeem/configuration/sections/alarms.py create mode 100644 redeem/configuration/sections/filamentsensors.py create mode 100644 redeem/configuration/sections/homing.py create mode 100644 redeem/configuration/sections/macros.py create mode 100644 redeem/configuration/sections/plugins.py create mode 100644 redeem/configuration/sections/probe.py create mode 100644 redeem/configuration/sections/rotaryencoders.py create mode 100644 redeem/configuration/sections/servos.py rename redeem/configuration/sections/{stepper.py => steppers.py} (90%) create mode 100644 redeem/configuration/sections/watchdog.py create mode 100644 tests/core/resources/default.2.0.cfg create mode 100644 tests/core/test_configs.py diff --git a/redeem/configuration/RedeemConfig.py b/redeem/configuration/RedeemConfig.py index 09e1269f..051bef1d 100644 --- a/redeem/configuration/RedeemConfig.py +++ b/redeem/configuration/RedeemConfig.py @@ -1,12 +1,21 @@ +from redeem.configuration.sections.alarms import AlarmsConfig from redeem.configuration.sections.coldends import ColdendsConfig from redeem.configuration.sections.delta import DeltaConfig from redeem.configuration.sections.endstops import EndstopsConfig from redeem.configuration.sections.fans import FansConfig +from redeem.configuration.sections.filamentsensors import FilamentSensorsConfig from redeem.configuration.sections.geometry import GeometryConfig from redeem.configuration.sections.heaters import HeatersConfig +from redeem.configuration.sections.homing import HomingConfig +from redeem.configuration.sections.macros import MacrosConfig from redeem.configuration.sections.planner import PlannerConfig -from redeem.configuration.sections.stepper import StepperConfig +from redeem.configuration.sections.plugins import HPX2MaxPluginConfig, DualServoPluginConfig +from redeem.configuration.sections.probe import ProbeConfig +from redeem.configuration.sections.rotaryencoders import RotaryEncodersConfig +from redeem.configuration.sections.servos import ServosConfig +from redeem.configuration.sections.steppers import SteppersConfig from redeem.configuration.sections.system import SystemConfig +from redeem.configuration.sections.watchdog import WatchdogConfig class RedeemConfig(object): @@ -14,30 +23,40 @@ class RedeemConfig(object): to make changes where `printer.config` is accessed in the code base. """ - system = SystemConfig() - - - - stepper = StepperConfig() + alarms = AlarmsConfig() + cold_ends = ColdendsConfig() + delta = DeltaConfig() + endstops = EndstopsConfig() + fans = FansConfig() + filament_sensors = FilamentSensorsConfig() + geometry = GeometryConfig() + heaters = HeatersConfig() + homing = HomingConfig() + macros = MacrosConfig() planner = PlannerConfig() - # system = SystemConfig - # .... + hpx2maxplugin = HPX2MaxPluginConfig() + dualservoplugin = DualServoPluginConfig() + probe = ProbeConfig() + rotary_encoders = RotaryEncodersConfig() + servos = ServosConfig() + steppers = SteppersConfig() + system = SystemConfig() + watchdog = WatchdogConfig() def get(self, section, key): - if hasattr(self, section.lower()): - return getattr(self, section.lower()).get(key) + if hasattr(self, section.replace('-', '_').lower()): + return getattr(self, section.replace('-', '_').lower()).get(key) return None + def has(self, section, key): + return hasattr(self, section.replace('-', '_').lower()) and getattr(self, section.replace('-', '_').lower()).has(key) + def getint(self, section, key): - if hasattr(self, section.lower()): - return getattr(self, section.lower()).getint(key) + if hasattr(self, section.replace('-', '_').lower()): + return getattr(self, section.replace('-', '_').lower()).getint(key) return None def getfloat(self, section, key): - if hasattr(self, section.lower()): - return getattr(self, section.lower()).getfloat(key) + if hasattr(self, section.replace('-', '_').lower()): + return getattr(self, section.replace('-', '_').lower()).getfloat(key) return None - - # . - # . - # . \ No newline at end of file diff --git a/redeem/configuration/factories/ConfigFactory.py b/redeem/configuration/factories/ConfigFactory.py new file mode 100644 index 00000000..b508d670 --- /dev/null +++ b/redeem/configuration/factories/ConfigFactory.py @@ -0,0 +1,70 @@ +from redeem.CascadingConfigParser import CascadingConfigParser +from redeem.configuration.RedeemConfig import RedeemConfig +from redeem.configuration.sections.alarms import AlarmsConfig +from redeem.configuration.sections.coldends import ColdendsConfig +from redeem.configuration.sections.delta import DeltaConfig +from redeem.configuration.sections.endstops import EndstopsConfig +from redeem.configuration.sections.fans import FansConfig +from redeem.configuration.sections.filamentsensors import FilamentSensorsConfig +from redeem.configuration.sections.geometry import GeometryConfig +from redeem.configuration.sections.heaters import HeatersConfig +from redeem.configuration.sections.homing import HomingConfig +from redeem.configuration.sections.macros import MacrosConfig +from redeem.configuration.sections.planner import PlannerConfig +from redeem.configuration.sections.plugins import HPX2MaxPluginConfig, DualServoPluginConfig +from redeem.configuration.sections.probe import ProbeConfig +from redeem.configuration.sections.rotaryencoders import RotaryEncodersConfig +from redeem.configuration.sections.servos import ServosConfig +from redeem.configuration.sections.steppers import SteppersConfig +from redeem.configuration.sections.system import SystemConfig +from redeem.configuration.sections.watchdog import WatchdogConfig + + +class ConfigFactory(object): + cpp = None + sections = [ + ('Alarms', AlarmsConfig), + ('Cold-ends', ColdendsConfig), + ('Delta', DeltaConfig), + ('Endstops', EndstopsConfig), + ('Fans', FansConfig), + ('Filament-sensors', FilamentSensorsConfig), + ('Geometry', GeometryConfig), + ('Heaters', HeatersConfig), + ('Homing', HomingConfig), + ('Macros', MacrosConfig), + ('Planner', PlannerConfig), + ('HPX2MaxPlugin', HPX2MaxPluginConfig), + ('DualServoPlugin', DualServoPluginConfig), + ('Probe', ProbeConfig), + ('Rotary-encoders', RotaryEncodersConfig), + ('Servos', ServosConfig), + ('Steppers', SteppersConfig), + ('System', SystemConfig), + ('Watchdog', WatchdogConfig) + ] + + def __init__(self, config_files): + self.ccp = CascadingConfigParser(config_files) + + def hydrate_config(self): + """Use default mapper, unless another one is specified by subclass""" + redeem_config = RedeemConfig() + for section_name, section_cls in self.sections: + hydration_name = 'hydrate_' + section_cls.__name__.lower() + if hasattr(self, hydration_name): + config_func = getattr(self, hydration_name) + config = config_func() + else: + config = self.hydrate_section_config(section_name, section_cls) + setattr(redeem_config, section_name.lower(), config) + return redeem_config + + def hydrate_section_config(self, section_name, config_cls): + """A simple one-to-one mapper from ini to config class""" + config = config_cls() + for item in config_cls.__dict__: + if item.startswith('__'): + continue + setattr(config, item, self.ccp.get(section_name, item)) + return config diff --git a/redeem/configuration/factories/ConfigFactoryV19.py b/redeem/configuration/factories/ConfigFactoryV19.py new file mode 100644 index 00000000..a65acbf8 --- /dev/null +++ b/redeem/configuration/factories/ConfigFactoryV19.py @@ -0,0 +1,5 @@ +from redeem.configuration.factories.ConfigFactory import ConfigFactory + + +class ConfigV19Factory(ConfigFactory): + pass diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/redeem/configuration/sections/__init__.py b/redeem/configuration/sections/__init__.py index 2fdabf94..52858cd7 100644 --- a/redeem/configuration/sections/__init__.py +++ b/redeem/configuration/sections/__init__.py @@ -1,13 +1,21 @@ +def _clean(key): + return key.replace('-','_').lower() + + class BaseConfig(object): """Superclass of all config 'sections'""" + def has(self, key): + return hasattr(self, _clean(key)) + def get(self, key): - if not getattr(self, key, None): + if not hasattr(self, _clean(key)): + print("*****{}".format(key)) return None - return getattr(self, key) + return getattr(self, _clean(key)) def getfloat(self, key): - val = self.get(key) + val = self.get(_clean(key)) try: val = float(val) except ValueError: @@ -15,7 +23,7 @@ def getfloat(self, key): return val def getint(self, key): - val = self.get(key) + val = self.get(_clean(key)) try: val = int(val) except ValueError: diff --git a/redeem/configuration/sections/alarms.py b/redeem/configuration/sections/alarms.py new file mode 100644 index 00000000..0cb6cc2d --- /dev/null +++ b/redeem/configuration/sections/alarms.py @@ -0,0 +1,5 @@ +from redeem.configuration.sections import BaseConfig + + +class AlarmsConfig(BaseConfig): + pass diff --git a/redeem/configuration/sections/coldends.py b/redeem/configuration/sections/coldends.py index 9101f086..23dd982d 100644 --- a/redeem/configuration/sections/coldends.py +++ b/redeem/configuration/sections/coldends.py @@ -8,45 +8,38 @@ class ColdendsConfig(BaseConfig): connect_ds18b20_0_fan_1 = False # This list is for connecting thermistors to fans, so they are controlled automatically when reaching 60 degrees. - connect_therm_E_fan_0 = False - connect_therm_E_fan_1 = False - connect_therm_E_fan_2 = False - connect_therm_E_fan_3 = False - connect_therm_H_fan_0 = False - connect_therm_H_fan_1 = False - connect_therm_H_fan_2 = False - connect_therm_H_fan_3 = False - connect_therm_A_fan_0 = False - connect_therm_A_fan_1 = False - connect_therm_A_fan_2 = False - connect_therm_A_fan_3 = False - connect_therm_B_fan_0 = False - connect_therm_B_fan_1 = False - connect_therm_B_fan_2 = False - connect_therm_B_fan_3 = False - connect_therm_C_fan_0 = False - connect_therm_C_fan_1 = False - connect_therm_C_fan_2 = False - connect_therm_C_fan_3 = False - connect_therm_HBP_fan_0 = False - connect_therm_HBP_fan_1 = False - connect_therm_HBP_fan_2 = False - connect_therm_HBP_fan_3 = False - - add_fan_0_to_M106 = False - add_fan_1_to_M106 = False - add_fan_2_to_M106 = False - add_fan_3_to_M106 = False + connect_therm_e_fan_0 = False + connect_therm_e_fan_1 = False + connect_therm_e_fan_2 = False + connect_therm_e_fan_3 = False + connect_therm_h_fan_0 = False + connect_therm_h_fan_1 = False + connect_therm_h_fan_2 = False + connect_therm_h_fan_3 = False + connect_therm_a_fan_0 = False + connect_therm_a_fan_1 = False + connect_therm_a_fan_2 = False + connect_therm_a_fan_3 = False + connect_therm_b_fan_0 = False + connect_therm_b_fan_1 = False + connect_therm_b_fan_2 = False + connect_therm_b_fan_3 = False + connect_therm_c_fan_0 = False + connect_therm_c_fan_1 = False + connect_therm_c_fan_2 = False + connect_therm_c_fan_3 = False + connect_therm_hbp_fan_0 = False + connect_therm_hbp_fan_1 = False + connect_therm_hbp_fan_2 = False + connect_therm_hbp_fan_3 = False + + add_fan_0_to_m106 = False + add_fan_1_to_m106 = False + add_fan_2_to_m106 = False + add_fan_3_to_m106 = False cooler_0_target_temp = 60 # If you want coolers to have a different 'keep' temp, list it here. # If you want the fan-thermitor connetions to have a # different temperature: - # therm-e-fan-0-target_temp = 70 - - def get(self, key): - key = key.replace('_', '-') - - if not getattr(self, key, None): - return None - return getattr(self, key) + # therm-e-fan-0-target_temp = 70 \ No newline at end of file diff --git a/redeem/configuration/sections/delta.py b/redeem/configuration/sections/delta.py index 1737a6d2..1339b62f 100644 --- a/redeem/configuration/sections/delta.py +++ b/redeem/configuration/sections/delta.py @@ -3,13 +3,13 @@ class DeltaConfig(BaseConfig): - L = 0.135 # Length of the rod (m) + l = 0.135 # Length of the rod (m) r = 0.144 # Radius of the columns (m) # Compensation for positional error of the columns # Radial offsets of the columns, positive values move the tower away from the center of the printer (m) - A_radial, B_radial, C_radial = 0.0, 0.0, 0.0 + a_radial, b_radial, c_radial = 0.0, 0.0, 0.0 # Angular offsets of the columns # Positive values move the tower counter-clockwise, as seen from above (degrees) - A_angular, B_angular, C_angular = 0.0, 0.0, 0.0 + a_angular, b_angular, c_angular = 0.0, 0.0, 0.0 diff --git a/redeem/configuration/sections/endstops.py b/redeem/configuration/sections/endstops.py index f39cc394..cde24f80 100644 --- a/redeem/configuration/sections/endstops.py +++ b/redeem/configuration/sections/endstops.py @@ -22,26 +22,26 @@ class EndstopsConfig(BaseConfig): # Invert = # True means endstop is connected as Normally Open (NO) or not connected # False means endstop is connected as Normally Closed (NC) - invert_X1 = False - invert_X2 = False - invert_Y1 = False - invert_Y2 = False - invert_Z1 = False - invert_Z2 = False + invert_x1 = False + invert_x2 = False + invert_y1 = False + invert_y2 = False + invert_z1 = False + invert_z2 = False - pin_X1 = 'GPIO3_21' - pin_X2 = 'GPIO0_30' - pin_Y1 = 'GPIO1_17' - pin_Y2 = 'GPIO3_17' - pin_Z1 = 'GPIO0_31' - pin_Z2 = 'GPIO0_4' + pin_x1 = 'GPIO3_21' + pin_x2 = 'GPIO0_30' + pin_y1 = 'GPIO1_17' + pin_y2 = 'GPIO3_17' + pin_z1 = 'GPIO0_31' + pin_z2 = 'GPIO0_4' - keycode_X1 = 112 - keycode_X2 = 113 - keycode_Y1 = 114 - keycode_Y2 = 115 - keycode_Z1 = 116 - keycode_Z2 = 117 + keycode_x1 = 112 + keycode_x2 = 113 + keycode_y1 = 114 + keycode_y2 = 115 + keycode_z1 = 116 + keycode_z2 = 117 # If one endstop is hit, which steppers and directions are masked. # The list is comma separated and has format @@ -56,12 +56,12 @@ class EndstopsConfig(BaseConfig): # end_stop_X1_stops = x_neg, end_stop_X2_stops = x_pos, ... # For CoreXY and similar, two steppers should be stopped if an end stop is hit. # similarly for a delta probe should stop x, y and z. - end_stop_X1_stops = None - end_stop_Y1_stops = None - end_stop_Z1_stops = None - end_stop_X2_stops = None - end_stop_Y2_stops = None - end_stop_Z2_stops = None + end_stop_x1_stops = '' + end_stop_y1_stops = '' + end_stop_z1_stops = '' + end_stop_x2_stops = '' + end_stop_y2_stops = '' + end_stop_z2_stops = '' # if an endstop should only be used for homing or probing, then add it to # homing_only_endstops in comma separated format. @@ -69,7 +69,7 @@ class EndstopsConfig(BaseConfig): # this will make sure that endstop Z1 and Z2 are only used during homing or probing # NOTE: Be very careful with this option. - homing_only_endstops = None + homing_only_endstops = '' soft_end_stop_min_x = -0.5 soft_end_stop_min_y = -0.5 @@ -87,4 +87,4 @@ class EndstopsConfig(BaseConfig): soft_end_stop_max_h = 1000.0 soft_end_stop_max_a = 1000.0 soft_end_stop_max_b = 1000.0 - soft_end_stop_max_c = 1000.0 \ No newline at end of file + soft_end_stop_max_c = 1000.0 diff --git a/redeem/configuration/sections/fans.py b/redeem/configuration/sections/fans.py index 3cc133cb..486a5003 100644 --- a/redeem/configuration/sections/fans.py +++ b/redeem/configuration/sections/fans.py @@ -12,10 +12,3 @@ class FansConfig(BaseConfig): default_fan_7_value = 0.0 default_fan_8_value = 0.0 default_fan_9_value = 0.0 - - def get(self, key): - key = key.replace('_', '-') - - if not getattr(self, key, None): - return None - return getattr(self, key) diff --git a/redeem/configuration/sections/filamentsensors.py b/redeem/configuration/sections/filamentsensors.py new file mode 100644 index 00000000..95202985 --- /dev/null +++ b/redeem/configuration/sections/filamentsensors.py @@ -0,0 +1,7 @@ +from redeem.configuration.sections import BaseConfig + + +class FilamentSensorsConfig(BaseConfig): + + # If the error is > 1 cm, sound the alarm + alarm_level_e = 0.01 diff --git a/redeem/configuration/sections/heaters.py b/redeem/configuration/sections/heaters.py index 81ad74e7..12ea7609 100644 --- a/redeem/configuration/sections/heaters.py +++ b/redeem/configuration/sections/heaters.py @@ -4,92 +4,92 @@ class HeatersConfig(BaseConfig): # For list of available temp charts, look in temp_chart.py - sensor_E = 'B57560G104F' - pid_Kp_E = 0.1 - pid_Ti_E = 100.0 - pid_Td_E = 0.3 - ok_range_E = 4.0 - max_rise_temp_E = 10.0 - max_fall_temp_E = 10.0 - min_temp_E = 20.0 - max_temp_E = 250.0 - path_adc_E = '/sys/bus/iio/devices/iio:device0/in_voltage4_raw' - mosfet_E = 5 - onoff_E = False - prefix_E = 'T0' - max_power_E = 1.0 + sensor_e = 'B57560G104F' + pid_kp_e = 0.1 + pid_ti_e = 100.0 + pid_td_e = 0.3 + ok_range_e = 4.0 + max_rise_temp_e = 10.0 + max_fall_temp_e = 10.0 + min_temp_e = 20.0 + max_temp_e = 250.0 + path_adc_e = '/sys/bus/iio/devices/iio:device0/in_voltage4_raw' + mosfet_e = 5 + onoff_e = False + prefix_e = 'T0' + max_power_e = 1.0 - sensor_H = 'B57560G104F' - pid_Kp_H = 0.1 - pid_Ti_H = 0.01 - pid_Td_H = 0.3 - ok_range_H = 4.0 - max_rise_temp_H = 10.0 - max_fall_temp_H = 10.0 - min_temp_H = 20.0 - max_temp_H = 250.0 - path_adc_H = '/sys/bus/iio/devices/iio:device0/in_voltage5_raw' - mosfet_H = 3 - onoff_H = False - prefix_H = 'T1' - max_power_H = 1.0 + sensor_h = 'B57560G104F' + pid_kp_h = 0.1 + pid_ti_h = 0.01 + pid_td_h = 0.3 + ok_range_h = 4.0 + max_rise_temp_h = 10.0 + max_fall_temp_h = 10.0 + min_temp_h = 20.0 + max_temp_h = 250.0 + path_adc_h = '/sys/bus/iio/devices/iio:device0/in_voltage5_raw' + mosfet_h = 3 + onoff_h = False + prefix_h = 'T1' + max_power_h = 1.0 - sensor_A = 'B57560G104F' - pid_Kp_A = 0.1 - pid_Ti_A = 0.01 - pid_Td_A = 0.3 - ok_range_A = 4.0 - max_rise_temp_A = 10.0 - max_fall_temp_A = 10.0 - min_temp_A = 20.0 - max_temp_A = 250.0 - path_adc_A = '/sys/bus/iio/devices/iio:device0/in_voltage0_raw' - mosfet_A = 11 - onoff_A = False - prefix_A = 'T2' - max_power_A = 1.0 + sensor_a = 'B57560G104F' + pid_kp_a = 0.1 + pid_ti_a = 0.01 + pid_td_a = 0.3 + ok_range_a = 4.0 + max_rise_temp_a = 10.0 + max_fall_temp_a = 10.0 + min_temp_a = 20.0 + max_temp_a = 250.0 + path_adc_a = '/sys/bus/iio/devices/iio:device0/in_voltage0_raw' + mosfet_a = 11 + onoff_a = False + prefix_a = 'T2' + max_power_a = 1.0 - sensor_B = 'B57560G104F' - pid_Kp_B = 0.1 - pid_Ti_B = 0.01 - pid_Td_B = 0.3 - ok_range_B = 4.0 - max_rise_temp_B = 10.0 - max_fall_temp_B = 10.0 - min_temp_B = 20.0 - max_temp_B = 250.0 - path_adc_B = '/sys/bus/iio/devices/iio:device0/in_voltage3_raw' - mosfet_B = 12 - onoff_B = False - prefix_B = 'T3' - max_power_B = 1.0 + sensor_b = 'B57560G104F' + pid_kp_b = 0.1 + pid_ti_b = 0.01 + pid_td_b = 0.3 + ok_range_b = 4.0 + max_rise_temp_b = 10.0 + max_fall_temp_b = 10.0 + min_temp_b = 20.0 + max_temp_b = 250.0 + path_adc_b = '/sys/bus/iio/devices/iio:device0/in_voltage3_raw' + mosfet_b = 12 + onoff_b = False + prefix_b = 'T3' + max_power_b = 1.0 - sensor_C = 'B57560G104F' - pid_Kp_C = 0.1 - pid_Ti_C = 0.01 - pid_Td_C = 0.3 - ok_range_C = 4.0 - max_rise_temp_C = 10.0 - max_fall_temp_C = 10.0 - min_temp_C = 20.0 - max_temp_C = 250.0 - path_adc_C = '/sys/bus/iio/devices/iio:device0/in_voltage2_raw' - mosfet_C = 13 - onoff_C = False - prefix_C = 'T4' - max_power_C = 1.0 + sensor_c = 'B57560G104F' + pid_kp_c = 0.1 + pid_ti_c = 0.01 + pid_td_c = 0.3 + ok_range_c = 4.0 + max_rise_temp_c = 10.0 + max_fall_temp_c = 10.0 + min_temp_c = 20.0 + max_temp_c = 250.0 + path_adc_c = '/sys/bus/iio/devices/iio:device0/in_voltage2_raw' + mosfet_c = 13 + onoff_c = False + prefix_c = 'T4' + max_power_c = 1.0 - sensor_HBP = 'B57560G104F' - pid_Kp_HBP = 0.1 - pid_Ti_HBP = 0.01 - pid_Td_HBP = 0.3 - ok_range_HBP = 4.0 - max_rise_temp_HBP = 10.0 - max_fall_temp_HBP = 10.0 - min_temp_HBP = 20.0 - max_temp_HBP = 250.0 - path_adc_HBP = '/sys/bus/iio/devices/iio:device0/in_voltage6_raw' - mosfet_HBP = 4 - onoff_HBP = False - prefix_HBP = 'B' - max_power_HBP = 1.0 + sensor_hbp = 'B57560G104F' + pid_kp_hbp = 0.1 + pid_ti_hbp = 0.01 + pid_td_hbp = 0.3 + ok_range_hbp = 4.0 + max_rise_temp_hbp = 10.0 + max_fall_temp_hbp = 10.0 + min_temp_hbp = 20.0 + max_temp_hbp = 250.0 + path_adc_hbp = '/sys/bus/iio/devices/iio:device0/in_voltage6_raw' + mosfet_hbp = 4 + onoff_hbp = False + prefix_hbp = 'B' + max_power_hbp = 1.0 diff --git a/redeem/configuration/sections/homing.py b/redeem/configuration/sections/homing.py new file mode 100644 index 00000000..05a993ee --- /dev/null +++ b/redeem/configuration/sections/homing.py @@ -0,0 +1,53 @@ +from redeem.configuration.sections import BaseConfig + + +class HomingConfig(BaseConfig): + # default G28 homing axes + g28_default_axes = "X,Y,Z,E,H,A,B,C" + + # Homing speed for the steppers in m/s + # Search to minimum ends by default. Negative value for searching to maximum ends. + home_speed_x = 0.1 + home_speed_y = 0.1 + home_speed_z = 0.1 + home_speed_e = 0.01 + home_speed_h = 0.01 + home_speed_a = 0.01 + home_speed_b = 0.01 + home_speed_c = 0.01 + + # homing backoff speed + home_backoff_speed_x = 0.01 + home_backoff_speed_y = 0.01 + home_backoff_speed_z = 0.01 + home_backoff_speed_e = 0.01 + home_backoff_speed_h = 0.01 + home_backoff_speed_a = 0.01 + home_backoff_speed_b = 0.01 + home_backoff_speed_c = 0.01 + + # homing backoff dist + home_backoff_offset_x = 0.01 + home_backoff_offset_y = 0.01 + home_backoff_offset_z = 0.01 + home_backoff_offset_e = 0.01 + home_backoff_offset_h = 0.01 + home_backoff_offset_a = 0.01 + home_backoff_offset_b = 0.01 + home_backoff_offset_c = 0.01 + + # Where should the printer goes after homing. + # The default is to stay at the offset position. + # This setting is useful if you have a delta printer + # and want it to stay at the top position after homing, instead + # of moving down to the center of the plate. + # In that case, use home_z and set that to the same as the offset values + # for X, Y, and Z, only with different sign. + home_x = 0.0 + home_y = 0.0 + home_z = 0.0 + home_e = 0.0 + home_h = 0.0 + home_a = 0.0 + home_b = 0.0 + home_c = 0.0 diff --git a/redeem/configuration/sections/macros.py b/redeem/configuration/sections/macros.py new file mode 100644 index 00000000..89aee6b6 --- /dev/null +++ b/redeem/configuration/sections/macros.py @@ -0,0 +1,46 @@ +from redeem.configuration.sections import BaseConfig + + +class MacrosConfig(BaseConfig): + g29 = """ + M561 ; Reset the bed level matrix + M558 P0 ; Set probe type to Servo with switch + M557 P0 X10 Y20 ; Set probe point 0 + M557 P1 X10 Y180 ; Set probe point 1 + M557 P2 X180 Y100 ; Set probe point 2 + G28 X0 Y0 ; Home X Y + + G28 Z0 ; Home Z + G0 Z12 ; Move Z up to allow space for probe + G32 ; Undock probe + G92 Z0 ; Reset Z height to 0 + G30 P0 S ; Probe point 0 + G0 Z0 ; Move the Z up + G31 ; Dock probe + + G28 Z0 ; Home Z + G0 Z12 ; Move Z up to allow space for probe + G32 ; Undock probe + G92 Z0 ; Reset Z height to 0 + G30 P1 S ; Probe point 1 + G0 Z0 ; Move the Z up + G31 ; Dock probe + + G28 Z0 ; Home Z + G0 Z12 ; Move Z up to allow space for probe + G32 ; Undock probe + G92 Z0 ; Reset Z height to 0 + G30 P2 S ; Probe point 2 + G0 Z0 ; Move the Z up + G31 ; Dock probe + + G28 X0 Y0 ; Home X Y + """ + + g31 = """ + M280 P0 S320 F3000 ; Probe up (Dock sled) + """ + + g32 = """ + M280 P0 S-60 F3000 ; Probe down (Undock sled) + """ diff --git a/redeem/configuration/sections/plugins.py b/redeem/configuration/sections/plugins.py new file mode 100644 index 00000000..b5348f61 --- /dev/null +++ b/redeem/configuration/sections/plugins.py @@ -0,0 +1,30 @@ +from redeem.configuration.sections import BaseConfig + + +class HPX2MaxPluginConfig(BaseConfig): + + # Configuration for the HPX2Max plugin (if loaded) + # The channel on which the servo is connected. The numbering correspond to the Fan number + servo_channel = 1 + + # Extruder 0 angle to set the servo when extruder 0 is selected, in degree + extruder_0_angle = 20 + + # Extruder 1 angle to set the servo when extruder 1 is selected, in degree + extruder_1_angle = 175 + + +class DualServoPluginConfig(BaseConfig): + + # Configuration for the Dual extruder by servo plugin + # This config is only used if loaded. + + # The pin name of where the servo is located + servo_channel = "P9_14" + pulse_min = 0.001 + pulse_max = 0.002 + angle_min = -90 + angle_max = 90 + extruder_0_angle = -5 + extruder_1_angle = 5 + diff --git a/redeem/configuration/sections/probe.py b/redeem/configuration/sections/probe.py new file mode 100644 index 00000000..1fd3f687 --- /dev/null +++ b/redeem/configuration/sections/probe.py @@ -0,0 +1,11 @@ +from redeem.configuration.sections import BaseConfig + + +class ProbeConfig(BaseConfig): + + length = 0.01 + speed = 0.05 + accel = 0.1 + offset_x = 0.0 + offset_y = 0.0 + offset_z = 0.0 diff --git a/redeem/configuration/sections/rotaryencoders.py b/redeem/configuration/sections/rotaryencoders.py new file mode 100644 index 00000000..5960834f --- /dev/null +++ b/redeem/configuration/sections/rotaryencoders.py @@ -0,0 +1,8 @@ +from redeem.configuration.sections import BaseConfig + + +class RotaryEncodersConfig(BaseConfig): + enable_e = False + event_e = "/dev/input/event1" + cpr_e = -360 + diameter_e = 0.003 diff --git a/redeem/configuration/sections/servos.py b/redeem/configuration/sections/servos.py new file mode 100644 index 00000000..31ddd147 --- /dev/null +++ b/redeem/configuration/sections/servos.py @@ -0,0 +1,19 @@ +from redeem.configuration.sections import BaseConfig + + +class ServosConfig(BaseConfig): + + # Example servo for Rev A4A, connected to channel 14 on the PWM chip + # For Rev B, servo is either P9_14 or P9_16. + # Not enabled for now, just kept here for reference. + # Angle init is the angle the servo is set to when redeem starts. + # pulse min and max is the pulse with for min and max position, as always in SI unit Seconds. + # So 0.001 is 1 ms. + # Angle min and max is what angles those pulses correspond to. + servo_0_enable = False + servo_0_channel = "P9_14" + servo_0_angle_init = 90 + servo_0_angle_min = -90 + servo_0_angle_max = 90 + servo_0_pulse_min = 0.001 + servo_0_pulse_max = 0.002 diff --git a/redeem/configuration/sections/stepper.py b/redeem/configuration/sections/steppers.py similarity index 90% rename from redeem/configuration/sections/stepper.py rename to redeem/configuration/sections/steppers.py index 1f300ebc..8076e223 100644 --- a/redeem/configuration/sections/stepper.py +++ b/redeem/configuration/sections/steppers.py @@ -1,7 +1,7 @@ from redeem.configuration.sections import BaseConfig -class StepperConfig(BaseConfig): +class SteppersConfig(BaseConfig): microstepping_x, microstepping_y, microstepping_z = 3, 3, 3 microstepping_e, microstepping_h = 3, 3 @@ -39,9 +39,9 @@ class StepperConfig(BaseConfig): # meaning that it will mirror the position of the # specified stepper. Typically, H will mirror Y or Z, # in the case of the former, write this: slave_y = H. - slave_x, slave_y, slave_z = None, None, None - slave_e, slave_h = None, None - slave_a, slave_b, slave_c = None, None, None + slave_x, slave_y, slave_z = '', '', '' + slave_e, slave_h = '', '' + slave_a, slave_b, slave_c = '', '', '' # Stepper timout use_timeout = True diff --git a/redeem/configuration/sections/system.py b/redeem/configuration/sections/system.py index 5babadfe..24c20f6a 100644 --- a/redeem/configuration/sections/system.py +++ b/redeem/configuration/sections/system.py @@ -7,5 +7,5 @@ class SystemConfig(BaseConfig): log_to_file = True logfile = '/home/octo/.octoprint/logs/plugin_redeem.log' data_path = "/etc/redeem" # location to look for data files (temperature charts, etc) - plugins = [] # Plugin to load for redeem, comma separated (i.e. HPX2Max,plugin2,plugin3) - machine_type = None # Machine type is used by M115 to identify the machine connected. + plugins = '' # Plugin to load for redeem, comma separated (i.e. HPX2Max,plugin2,plugin3) + machine_type = 'Unknown' # Machine type is used by M115 to identify the machine connected. diff --git a/redeem/configuration/sections/watchdog.py b/redeem/configuration/sections/watchdog.py new file mode 100644 index 00000000..35e4a2b5 --- /dev/null +++ b/redeem/configuration/sections/watchdog.py @@ -0,0 +1,5 @@ +from redeem.configuration.sections import BaseConfig + + +class WatchdogConfig(BaseConfig): + enable_watchdog = True diff --git a/tests/core/resources/default.2.0.cfg b/tests/core/resources/default.2.0.cfg new file mode 100644 index 00000000..c51838dc --- /dev/null +++ b/tests/core/resources/default.2.0.cfg @@ -0,0 +1,616 @@ +[System] + +# CRITICAL=50, # ERROR=40, # WARNING=30, INFO=20, DEBUG=10, NOTSET=0 +loglevel = 20 + +# If set to True, also log to file. +log_to_file = True + +# Default file to log to, this can be viewed from octoprint +logfile = /home/octo/.octoprint/logs/plugin_redeem.log + +# location to look for data files (temperature charts, etc) +data_path = /etc/redeem + +# Plugin to load for redeem, comma separated (i.e. HPX2Max,plugin2,plugin3) +plugins = + +# Machine type is used by M115 +# to identify the machine connected. +machine_type = Unknown + +[Geometry] +# 0 - Cartesian +# 1 - H-belt +# 2 - Core XY +# 3 - Delta +axis_config = 0 + +# The total length each axis can travel +# This affects the homing endstop searching length. +travel_x = 0.2 +travel_y = 0.2 +travel_z = 0.2 +travel_e = 0.2 +travel_h = 0.2 +travel_a = 0.0 +travel_b = 0.0 +travel_c = 0.0 + +# Define the origin in relation to the endstops. +# The offset that the origin of the build plate has +# from the end stop. +offset_x = 0.0 +offset_y = 0.0 +offset_z = 0.0 +offset_e = 0.0 +offset_h = 0.0 +offset_a = 0.0 +offset_b = 0.0 +offset_c = 0.0 + + +bed_compensation_matrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] + +[Delta] +# Length of the rod +L = 0.135 +# Radius of the columns +r = 0.144 + +# Compensation for positional error of the columns + +# Radial offsets of the columns +# Positive values move the tower away from the center of the printer +A_radial = 0.0 +B_radial = 0.0 +C_radial = 0.0 + +# Angular offsets of the columns +# Positive values move the tower counter-clockwise, as seen from above +# Specified in degrees +A_angular = 0.0 +B_angular = 0.0 +C_angular = 0.0 + +# Stepper e is ext 1, h is ext 2 +[Steppers] + +microstepping_x = 3 +microstepping_y = 3 +microstepping_z = 3 +microstepping_e = 3 +microstepping_h = 3 +microstepping_a = 3 +microstepping_b = 3 +microstepping_c = 3 + +current_x = 0.5 +current_y = 0.5 +current_z = 0.5 +current_e = 0.5 +current_h = 0.5 +current_a = 0.5 +current_b = 0.5 +current_c = 0.5 + +# steps per mm: +# Defined how many stepper full steps needed to move 1mm. +# Do not factor in microstepping settings. +# For example: If the axis will travel 10mm in one revolution and +# angle per step in 1.8deg (200step/rev), steps_pr_mm is 20. +steps_pr_mm_x = 4.0 +steps_pr_mm_y = 4.0 +steps_pr_mm_z = 50.0 +steps_pr_mm_e = 6.0 +steps_pr_mm_h = 6.0 +steps_pr_mm_a = 6.0 +steps_pr_mm_b = 6.0 +steps_pr_mm_c = 6.0 + +backlash_x = 0.0 +backlash_y = 0.0 +backlash_z = 0.0 +backlash_e = 0.0 +backlash_h = 0.0 +backlash_a = 0.0 +backlash_b = 0.0 +backlash_c = 0.0 + + +# Which steppers are enabled +in_use_x = True +in_use_y = True +in_use_z = True +in_use_e = True +in_use_h = True +in_use_a = False +in_use_b = False +in_use_c = False + +# Set to -1 if axis is inverted +direction_x = 1 +direction_y = 1 +direction_z = 1 +direction_e = 1 +direction_h = 1 +direction_a = 1 +direction_b = 1 +direction_c = 1 + +# Set to True if slow decay mode is needed +slow_decay_x = 0 +slow_decay_y = 0 +slow_decay_z = 0 +slow_decay_e = 0 +slow_decay_h = 0 +slow_decay_a = 0 +slow_decay_b = 0 +slow_decay_c = 0 + + +# A stepper controller can operate in slave mode, +# meaning that it will mirror the position of the +# specified stepper. Typically, H will mirror Y or Z, +# in the case of the former, write this: slave_y = H. +slave_x = +slave_y = +slave_z = +slave_e = +slave_h = +slave_a = +slave_b = +slave_c = + +# Stepper timout +use_timeout = True +timeout_seconds = 500 + +[Planner] + +# size of the path planning cache +move_cache_size = 1024 + +# time to wait for buffer to fill, (ms) +print_move_buffer_wait = 250 + +# total buffered move time should not exceed this much (ms) +max_buffered_move_time = 1000 + +acceleration_x = 0.5 +acceleration_y = 0.5 +acceleration_z = 0.5 +acceleration_e = 0.5 +acceleration_h = 0.5 +acceleration_a = 0.5 +acceleration_b = 0.5 +acceleration_c = 0.5 + +max_jerk_x = 0.01 +max_jerk_y = 0.01 +max_jerk_z = 0.01 +max_jerk_e = 0.01 +max_jerk_h = 0.01 +max_jerk_a = 0.01 +max_jerk_b = 0.01 +max_jerk_c = 0.01 + +# Max speed for the steppers in m/s +max_speed_x = 0.2 +max_speed_y = 0.2 +max_speed_z = 0.02 +max_speed_e = 0.2 +max_speed_h = 0.2 +max_speed_a = 0.2 +max_speed_b = 0.2 +max_speed_c = 0.2 + +# for arc commands, seperate into segments of length in m +arc_segment_length = 0.001 + +# When true, movements on the E axis (eg, G1, G92) will apply +# to the active tool (similar to other firmwares). When false, +# such movements will only apply to the E axis. +e_axis_active = True + +[Cold-ends] +# To use the DS18B20 temp sensors, connect them like this. +# Enable by setting to True +connect-ds18b20-0-fan-0 = False +connect-ds18b20-1-fan-0 = False +connect-ds18b20-0-fan-1 = False + +# This list is for connecting thermistors to fans, +# so they are controlled automatically when reaching 60 degrees. +connect-therm-E-fan-0 = False +connect-therm-E-fan-1 = False +connect-therm-E-fan-2 = False +connect-therm-E-fan-3 = False +connect-therm-H-fan-0 = False +connect-therm-H-fan-1 = False +connect-therm-H-fan-2 = False +connect-therm-H-fan-3 = False +connect-therm-A-fan-0 = False +connect-therm-A-fan-1 = False +connect-therm-A-fan-2 = False +connect-therm-A-fan-3 = False +connect-therm-B-fan-0 = False +connect-therm-B-fan-1 = False +connect-therm-B-fan-2 = False +connect-therm-B-fan-3 = False +connect-therm-C-fan-0 = False +connect-therm-C-fan-1 = False +connect-therm-C-fan-2 = False +connect-therm-C-fan-3 = False +connect-therm-HBP-fan-0 = False +connect-therm-HBP-fan-1 = False +connect-therm-HBP-fan-2 = False +connect-therm-HBP-fan-3 = False + +add-fan-0-to-M106 = False +add-fan-1-to-M106 = False +add-fan-2-to-M106 = False +add-fan-3-to-M106 = False + +# If you want coolers to +# have a different 'keep' temp, list it here. +cooler_0_target_temp = 60 + +# If you want the fan-thermitor connetions to have a +# different temperature: +# therm-e-fan-0-target_temp = 70 + +[Fans] +default-fan-0-value = 0.0 +default-fan-1-value = 0.0 +default-fan-2-value = 0.0 +default-fan-3-value = 0.0 +default-fan-3-value = 0.0 +default-fan-4-value = 0.0 +default-fan-5-value = 0.0 +default-fan-6-value = 0.0 +default-fan-7-value = 0.0 +default-fan-8-value = 0.0 +default-fan-9-value = 0.0 + +[Heaters] +# For list of available temp charts, look in temp_chart.py + +sensor_E = B57560G104F +pid_Kp_E = 0.1 +pid_Ti_E = 100.0 +pid_Td_E = 0.3 +ok_range_E = 4.0 +max_rise_temp_E = 10.0 +max_fall_temp_E = 10.0 +min_temp_E = 20.0 +max_temp_E = 250.0 +path_adc_E = /sys/bus/iio/devices/iio:device0/in_voltage4_raw +mosfet_E = 5 +onoff_E = False +prefix_E = T0 +max_power_E = 1.0 + +sensor_H = B57560G104F +pid_Kp_H = 0.1 +pid_Ti_H = 0.01 +pid_Td_H = 0.3 +ok_range_H = 4.0 +max_rise_temp_H = 10.0 +max_fall_temp_H = 10.0 +min_temp_H = 20.0 +max_temp_H = 250.0 +path_adc_H = /sys/bus/iio/devices/iio:device0/in_voltage5_raw +mosfet_H = 3 +onoff_H = False +prefix_H = T1 +max_power_H = 1.0 + +sensor_A = B57560G104F +pid_Kp_A = 0.1 +pid_Ti_A = 0.01 +pid_Td_A = 0.3 +ok_range_A = 4.0 +max_rise_temp_A = 10.0 +max_fall_temp_A = 10.0 +min_temp_A = 20.0 +max_temp_A = 250.0 +path_adc_A = /sys/bus/iio/devices/iio:device0/in_voltage0_raw +mosfet_A = 11 +onoff_A = False +prefix_A = T2 +max_power_A = 1.0 + +sensor_B = B57560G104F +pid_Kp_B = 0.1 +pid_Ti_B = 0.01 +pid_Td_B = 0.3 +ok_range_B = 4.0 +max_rise_temp_B = 10.0 +max_fall_temp_B = 10.0 +min_temp_B = 20.0 +max_temp_B = 250.0 +path_adc_B = /sys/bus/iio/devices/iio:device0/in_voltage3_raw +mosfet_B = 12 +onoff_B = False +prefix_B = T3 +max_power_B = 1.0 + +sensor_C = B57560G104F +pid_Kp_C = 0.1 +pid_Ti_C = 0.01 +pid_Td_C = 0.3 +ok_range_C = 4.0 +max_rise_temp_C = 10.0 +max_fall_temp_C = 10.0 +min_temp_C = 20.0 +max_temp_C = 250.0 +path_adc_C = /sys/bus/iio/devices/iio:device0/in_voltage2_raw +mosfet_C = 13 +onoff_C = False +prefix_C = T4 +max_power_C = 1.0 + +sensor_HBP = B57560G104F +pid_Kp_HBP = 0.1 +pid_Ti_HBP = 0.01 +pid_Td_HBP = 0.3 +ok_range_HBP = 4.0 +max_rise_temp_HBP = 10.0 +max_fall_temp_HBP = 10.0 +min_temp_HBP = 20.0 +max_temp_HBP = 250.0 +path_adc_HBP = /sys/bus/iio/devices/iio:device0/in_voltage6_raw +mosfet_HBP = 4 +onoff_HBP = False +prefix_HBP = B +max_power_HBP = 1.0 + +[Endstops] +# Which axis should be homed. +has_x = True +has_y = True +has_z = True +has_e = False +has_h = False +has_a = False +has_b = False +has_c = False + +inputdev = /dev/input/by-path/platform-ocp:gpio_keys-event + +# Number of cycles to wait between checking +# end stops. CPU frequency is 200 MHz +end_stop_delay_cycles = 1000 + +# Invert = +# True means endstop is connected as Normally Open (NO) or not connected +# False means endstop is connected as Normally Closed (NC) +invert_X1 = False +invert_X2 = False +invert_Y1 = False +invert_Y2 = False +invert_Z1 = False +invert_Z2 = False + +pin_X1 = GPIO3_21 +pin_X2 = GPIO0_30 +pin_Y1 = GPIO1_17 +pin_Y2 = GPIO3_17 +pin_Z1 = GPIO0_31 +pin_Z2 = GPIO0_4 + +# On A4A you'll need pin_Y2 = GPIO1_19 + +keycode_X1 = 112 +keycode_X2 = 113 +keycode_Y1 = 114 +keycode_Y2 = 115 +keycode_Z1 = 116 +keycode_Z2 = 117 + +# If one endstop is hit, which steppers and directions are masked. +# The list is comma separated and has format +# x_cw = stepper x clockwise (independent of direction_x) +# x_ccw = stepper x counter clockwise (independent of direction_x) +# x_neg = setpper x negative direction (affected by direction_x) +# x_pos = setpper x positive direction (affected by direction_x) +# Steppers e and h (and a, b, c for reach) can also be masked. +# +# For a list of steppers to stop, use this format: x_cw, y_ccw +# For Simple XYZ bot, the usual practice would be +# end_stop_X1_stops = x_neg, end_stop_X2_stops = x_pos, ... +# For CoreXY and similar, two steppers should be stopped if an end stop is hit. +# similarly for a delta probe should stop x, y and z. +end_stop_X1_stops = +end_stop_Y1_stops = +end_stop_Z1_stops = +end_stop_X2_stops = +end_stop_Y2_stops = +end_stop_Z2_stops = + +# if an endstop should only be used for homing or probing, then add it to +# homing_only_endstops in comma separated format. +# Example: homing_only_endstops = Z1, Z2 +# this will make sure that endstop Z1 and Z2 are only used during homing or probing +# NOTE: Be very careful with this option. + +homing_only_endstops = + +soft_end_stop_min_x = -0.5 +soft_end_stop_min_y = -0.5 +soft_end_stop_min_z = -0.5 +soft_end_stop_min_e = -1000.0 +soft_end_stop_min_h = -1000.0 +soft_end_stop_min_a = -1000.0 +soft_end_stop_min_b = -1000.0 +soft_end_stop_min_c = -1000.0 + +soft_end_stop_max_x = 0.5 +soft_end_stop_max_y = 0.5 +soft_end_stop_max_z = 0.5 +soft_end_stop_max_e = 1000.0 +soft_end_stop_max_h = 1000.0 +soft_end_stop_max_a = 1000.0 +soft_end_stop_max_b = 1000.0 +soft_end_stop_max_c = 1000.0 + +[Homing] + +# default G28 homing axes +G28_default_axes = X,Y,Z,E,H,A,B,C + +# Homing speed for the steppers in m/s +# Search to minimum ends by default. Negative value for searching to maximum ends. +home_speed_x = 0.1 +home_speed_y = 0.1 +home_speed_z = 0.1 +home_speed_e = 0.01 +home_speed_h = 0.01 +home_speed_a = 0.01 +home_speed_b = 0.01 +home_speed_c = 0.01 + +# homing backoff speed +home_backoff_speed_x = 0.01 +home_backoff_speed_y = 0.01 +home_backoff_speed_z = 0.01 +home_backoff_speed_e = 0.01 +home_backoff_speed_h = 0.01 +home_backoff_speed_a = 0.01 +home_backoff_speed_b = 0.01 +home_backoff_speed_c = 0.01 + +# homing backoff dist +home_backoff_offset_x = 0.01 +home_backoff_offset_y = 0.01 +home_backoff_offset_z = 0.01 +home_backoff_offset_e = 0.01 +home_backoff_offset_h = 0.01 +home_backoff_offset_a = 0.01 +home_backoff_offset_b = 0.01 +home_backoff_offset_c = 0.01 + +# Where should the printer goes after homing. +# The default is to stay at the offset position. +# This setting is useful if you have a delta printer +# and want it to stay at the top position after homing, instead +# of moving down to the center of the plate. +# In that case, use home_z and set that to the same as the offset values +# for X, Y, and Z, only with different sign. +home_x = 0.0 +home_y = 0.0 +home_z = 0.0 +home_e = 0.0 +home_h = 0.0 +home_a = 0.0 +home_b = 0.0 +home_c = 0.0 + +[Servos] +# Example servo for Rev A4A, connected to channel 14 on the PWM chip +# For Rev B, servo is either P9_14 or P9_16. +# Not enabled for now, just kept here for reference. +# Angle init is the angle the servo is set to when redeem starts. +# pulse min and max is the pulse with for min and max position, as always in SI unit Seconds. +# So 0.001 is 1 ms. +# Angle min and max is what angles those pulses correspond to. +servo_0_enable = False +servo_0_channel = P9_14 +servo_0_angle_init = 90 +servo_0_angle_min = -90 +servo_0_angle_max = 90 +servo_0_pulse_min = 0.001 +servo_0_pulse_max = 0.002 + +[Probe] +length = 0.01 +speed = 0.05 +accel = 0.1 +offset_x = 0.0 +offset_y = 0.0 +offset_z = 0.0 + +[Rotary-encoders] +enable-e = False +event-e = /dev/input/event1 +cpr-e = -360 +diameter-e = 0.003 + +[Filament-sensors] +# If the error is > 1 cm, sound the alarm +alarm-level-e = 0.01 + +[Watchdog] +enable_watchdog = True + +[Alarms] + + +[Macros] +G29 = + M561 ; Reset the bed level matrix + M558 P0 ; Set probe type to Servo with switch + M557 P0 X10 Y20 ; Set probe point 0 + M557 P1 X10 Y180 ; Set probe point 1 + M557 P2 X180 Y100 ; Set probe point 2 + G28 X0 Y0 ; Home X Y + + G28 Z0 ; Home Z + G0 Z12 ; Move Z up to allow space for probe + G32 ; Undock probe + G92 Z0 ; Reset Z height to 0 + G30 P0 S ; Probe point 0 + G0 Z0 ; Move the Z up + G31 ; Dock probe + + G28 Z0 ; Home Z + G0 Z12 ; Move Z up to allow space for probe + G32 ; Undock probe + G92 Z0 ; Reset Z height to 0 + G30 P1 S ; Probe point 1 + G0 Z0 ; Move the Z up + G31 ; Dock probe + + G28 Z0 ; Home Z + G0 Z12 ; Move Z up to allow space for probe + G32 ; Undock probe + G92 Z0 ; Reset Z height to 0 + G30 P2 S ; Probe point 2 + G0 Z0 ; Move the Z up + G31 ; Dock probe + + G28 X0 Y0 ; Home X Y + +G31 = + M280 P0 S320 F3000 ; Probe up (Dock sled) + +G32 = + M280 P0 S-60 F3000 ; Probe down (Undock sled) + + +# Configuration for the HPX2Max plugin (if loaded) +[HPX2MaxPlugin] + +# The channel on which the servo is connected. The numbering correspond to the Fan number +servo_channel = 1 + +# Extruder 0 angle to set the servo when extruder 0 is selected, in degree +extruder_0_angle = 20 + +# Extruder 1 angle to set the servo when extruder 1 is selected, in degree +extruder_1_angle = 175 + + + +# Configuration for the Dual extruder by servo plugin +# This config is only used if loaded. +[DualServoPlugin] +# The pin name of where the servo is located +servo_channel = P9_14 +pulse_min = 0.001 +pulse_max = 0.002 +angle_min = -90 +angle_max = 90 +extruder_0_angle = -5 +extruder_1_angle = 5 diff --git a/tests/core/test_configs.py b/tests/core/test_configs.py new file mode 100644 index 00000000..2d3db907 --- /dev/null +++ b/tests/core/test_configs.py @@ -0,0 +1,30 @@ +import unittest +from ConfigParser import SafeConfigParser, RawConfigParser + +import os + +from redeem.configuration.RedeemConfig import RedeemConfig + +current_path = os.path.dirname(os.path.abspath(__file__)) + + +class ConfigTests(unittest.TestCase): + + def test_all_keys_in_config(self): + + default_cfg = os.path.join(current_path, 'resources/default.2.0.cfg') + cp = RawConfigParser() + self.assertEqual(len(cp.read(default_cfg)), 1) + + redeem_config = RedeemConfig() + + for section in cp.sections(): + for option in cp.options(section): + self.assertTrue(redeem_config.has(section, option), 'option {}/{} is missing'.format(section, option)) + + if section == "Macros": + continue + + config_val = str(cp.get(section, option)) + redeem_val = str(redeem_config.get(section, option)) + self.assertEqual(config_val, redeem_val, "option {}/{} does not match: '{}' vs '{}'".format(section, option, config_val, redeem_val)) From bc2d30842ba733c7e647f4aa0ccf0efda1d5ec75 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Thu, 26 Oct 2017 09:00:09 -0400 Subject: [PATCH 03/14] adding config factories --- .../configuration/factories/ConfigFactory.py | 70 ------------------ .../factories/ConfigFactoryV19.py | 4 +- .../factories/ConfigFactoryV20.py | 5 ++ redeem/configuration/factories/__init__.py | 71 +++++++++++++++++++ tests/core/test_configs.py | 32 +++++++-- 5 files changed, 103 insertions(+), 79 deletions(-) delete mode 100644 redeem/configuration/factories/ConfigFactory.py create mode 100644 redeem/configuration/factories/ConfigFactoryV20.py diff --git a/redeem/configuration/factories/ConfigFactory.py b/redeem/configuration/factories/ConfigFactory.py deleted file mode 100644 index b508d670..00000000 --- a/redeem/configuration/factories/ConfigFactory.py +++ /dev/null @@ -1,70 +0,0 @@ -from redeem.CascadingConfigParser import CascadingConfigParser -from redeem.configuration.RedeemConfig import RedeemConfig -from redeem.configuration.sections.alarms import AlarmsConfig -from redeem.configuration.sections.coldends import ColdendsConfig -from redeem.configuration.sections.delta import DeltaConfig -from redeem.configuration.sections.endstops import EndstopsConfig -from redeem.configuration.sections.fans import FansConfig -from redeem.configuration.sections.filamentsensors import FilamentSensorsConfig -from redeem.configuration.sections.geometry import GeometryConfig -from redeem.configuration.sections.heaters import HeatersConfig -from redeem.configuration.sections.homing import HomingConfig -from redeem.configuration.sections.macros import MacrosConfig -from redeem.configuration.sections.planner import PlannerConfig -from redeem.configuration.sections.plugins import HPX2MaxPluginConfig, DualServoPluginConfig -from redeem.configuration.sections.probe import ProbeConfig -from redeem.configuration.sections.rotaryencoders import RotaryEncodersConfig -from redeem.configuration.sections.servos import ServosConfig -from redeem.configuration.sections.steppers import SteppersConfig -from redeem.configuration.sections.system import SystemConfig -from redeem.configuration.sections.watchdog import WatchdogConfig - - -class ConfigFactory(object): - cpp = None - sections = [ - ('Alarms', AlarmsConfig), - ('Cold-ends', ColdendsConfig), - ('Delta', DeltaConfig), - ('Endstops', EndstopsConfig), - ('Fans', FansConfig), - ('Filament-sensors', FilamentSensorsConfig), - ('Geometry', GeometryConfig), - ('Heaters', HeatersConfig), - ('Homing', HomingConfig), - ('Macros', MacrosConfig), - ('Planner', PlannerConfig), - ('HPX2MaxPlugin', HPX2MaxPluginConfig), - ('DualServoPlugin', DualServoPluginConfig), - ('Probe', ProbeConfig), - ('Rotary-encoders', RotaryEncodersConfig), - ('Servos', ServosConfig), - ('Steppers', SteppersConfig), - ('System', SystemConfig), - ('Watchdog', WatchdogConfig) - ] - - def __init__(self, config_files): - self.ccp = CascadingConfigParser(config_files) - - def hydrate_config(self): - """Use default mapper, unless another one is specified by subclass""" - redeem_config = RedeemConfig() - for section_name, section_cls in self.sections: - hydration_name = 'hydrate_' + section_cls.__name__.lower() - if hasattr(self, hydration_name): - config_func = getattr(self, hydration_name) - config = config_func() - else: - config = self.hydrate_section_config(section_name, section_cls) - setattr(redeem_config, section_name.lower(), config) - return redeem_config - - def hydrate_section_config(self, section_name, config_cls): - """A simple one-to-one mapper from ini to config class""" - config = config_cls() - for item in config_cls.__dict__: - if item.startswith('__'): - continue - setattr(config, item, self.ccp.get(section_name, item)) - return config diff --git a/redeem/configuration/factories/ConfigFactoryV19.py b/redeem/configuration/factories/ConfigFactoryV19.py index a65acbf8..0decf264 100644 --- a/redeem/configuration/factories/ConfigFactoryV19.py +++ b/redeem/configuration/factories/ConfigFactoryV19.py @@ -1,5 +1,5 @@ -from redeem.configuration.factories.ConfigFactory import ConfigFactory +from redeem.configuration.factories import ConfigFactory -class ConfigV19Factory(ConfigFactory): +class ConfigFactoryV19(ConfigFactory): pass diff --git a/redeem/configuration/factories/ConfigFactoryV20.py b/redeem/configuration/factories/ConfigFactoryV20.py new file mode 100644 index 00000000..0045dbcf --- /dev/null +++ b/redeem/configuration/factories/ConfigFactoryV20.py @@ -0,0 +1,5 @@ +from redeem.configuration.factories import ConfigFactory + + +class ConfigFactoryV20(ConfigFactory): + pass diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index e69de29b..9c2e8918 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -0,0 +1,71 @@ +from redeem.CascadingConfigParser import CascadingConfigParser +from redeem.configuration.RedeemConfig import RedeemConfig +from redeem.configuration.sections.alarms import AlarmsConfig +from redeem.configuration.sections.coldends import ColdendsConfig +from redeem.configuration.sections.delta import DeltaConfig +from redeem.configuration.sections.endstops import EndstopsConfig +from redeem.configuration.sections.fans import FansConfig +from redeem.configuration.sections.filamentsensors import FilamentSensorsConfig +from redeem.configuration.sections.geometry import GeometryConfig +from redeem.configuration.sections.heaters import HeatersConfig +from redeem.configuration.sections.homing import HomingConfig +from redeem.configuration.sections.macros import MacrosConfig +from redeem.configuration.sections.planner import PlannerConfig +from redeem.configuration.sections.plugins import HPX2MaxPluginConfig, DualServoPluginConfig +from redeem.configuration.sections.probe import ProbeConfig +from redeem.configuration.sections.rotaryencoders import RotaryEncodersConfig +from redeem.configuration.sections.servos import ServosConfig +from redeem.configuration.sections.steppers import SteppersConfig +from redeem.configuration.sections.system import SystemConfig +from redeem.configuration.sections.watchdog import WatchdogConfig + + +class ConfigFactory(object): + cpp = None + sections = [ + ('Alarms', AlarmsConfig), + ('Cold-ends', ColdendsConfig), + ('Delta', DeltaConfig), + ('Endstops', EndstopsConfig), + ('Fans', FansConfig), + ('Filament-sensors', FilamentSensorsConfig), + ('Geometry', GeometryConfig), + ('Heaters', HeatersConfig), + ('Homing', HomingConfig), + ('Macros', MacrosConfig), + ('Planner', PlannerConfig), + ('HPX2MaxPlugin', HPX2MaxPluginConfig), + ('DualServoPlugin', DualServoPluginConfig), + ('Probe', ProbeConfig), + ('Rotary-encoders', RotaryEncodersConfig), + ('Servos', ServosConfig), + ('Steppers', SteppersConfig), + ('System', SystemConfig), + ('Watchdog', WatchdogConfig) + ] + + def __init__(self, config_files): + self.ccp = CascadingConfigParser(config_files) + + def hydrate_config(self): + """Use default mapper, unless another one is specified by subclass""" + redeem_config = RedeemConfig() + for section_name, section_cls in self.sections: + hydration_name = 'hydrate_' + section_cls.__name__.lower() + if hasattr(self, hydration_name): + config_func = getattr(self, hydration_name) + config = config_func() + else: + config = self.hydrate_section_config(section_name, section_cls) + setattr(redeem_config, section_name.lower(), config) + return redeem_config + + def hydrate_section_config(self, section_name, config_cls): + """A simple one-to-one mapper from ini to config class""" + config = config_cls() + for item in config_cls.__dict__: + if item.startswith('__'): + continue + item = item.replace('_', '-') + setattr(config, item, self.ccp.get(section_name, item)) + return config diff --git a/tests/core/test_configs.py b/tests/core/test_configs.py index 2d3db907..e89b04ee 100644 --- a/tests/core/test_configs.py +++ b/tests/core/test_configs.py @@ -4,19 +4,14 @@ import os from redeem.configuration.RedeemConfig import RedeemConfig +from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 current_path = os.path.dirname(os.path.abspath(__file__)) class ConfigTests(unittest.TestCase): - def test_all_keys_in_config(self): - - default_cfg = os.path.join(current_path, 'resources/default.2.0.cfg') - cp = RawConfigParser() - self.assertEqual(len(cp.read(default_cfg)), 1) - - redeem_config = RedeemConfig() + def _compare_configs(self, cp, redeem_config): for section in cp.sections(): for option in cp.options(section): @@ -28,3 +23,26 @@ def test_all_keys_in_config(self): config_val = str(cp.get(section, option)) redeem_val = str(redeem_config.get(section, option)) self.assertEqual(config_val, redeem_val, "option {}/{} does not match: '{}' vs '{}'".format(section, option, config_val, redeem_val)) + + def test_all_keys_in_config(self): + + default_cfg = os.path.join(current_path, 'resources/default.2.0.cfg') + cp = RawConfigParser() + self.assertEqual(len(cp.read(default_cfg)), 1) + + redeem_config = RedeemConfig() + self._compare_configs(cp, redeem_config) + + def test_basic_config_factory(self): + + files = [ + os.path.join(current_path, 'resources/default.2.0.cfg'), + ] + + factory = ConfigFactoryV20(files) + redeem_config = factory.hydrate_config() + + default_cfg = os.path.join(current_path, 'resources/default.2.0.cfg') + cp = RawConfigParser() + cp.read(default_cfg) + self._compare_configs(cp, redeem_config) From 0348af7911c3b7df2f50b0db4f4fe099525f2368 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Thu, 26 Oct 2017 10:40:37 -0400 Subject: [PATCH 04/14] adding additional tests, removing default config file (as all the defaults are now in python) and modifying Redeem to instantiate using config factory --- redeem/Redeem.py | 14 +- redeem/configuration/factories/__init__.py | 90 +++++---- .../core/resources/default.1.9.cfg | 56 +++--- tests/core/resources/local.cfg | 2 + tests/core/resources/printer.cfg | 187 ++++++++++++++++++ tests/core/test_configs.py | 45 ++++- tests/logger_test.py | 105 ++++++++++ 7 files changed, 422 insertions(+), 77 deletions(-) rename configs/default.cfg => tests/core/resources/default.1.9.cfg (94%) create mode 100644 tests/core/resources/local.cfg create mode 100644 tests/core/resources/printer.cfg create mode 100644 tests/logger_test.py diff --git a/redeem/Redeem.py b/redeem/Redeem.py index 1877988f..63894caf 100755 --- a/redeem/Redeem.py +++ b/redeem/Redeem.py @@ -67,6 +67,8 @@ from Watchdog import Watchdog # Global vars +from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 + printer = None # Default logging level is set to debug @@ -97,12 +99,6 @@ def __init__(self, config_location="/etc/redeem"): 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") @@ -110,10 +106,8 @@ def __init__(self, config_location="/etc/redeem"): 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')]) + config_factory = ConfigFactoryV20([os.path.join(config_location, 'printer.cfg'), os.path.join(config_location,'local.cfg')]) + printer.config = config_factory.hydrate_config() # Check the local and printer files printer_path = os.path.join(config_location,"printer.cfg") diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index 9c2e8918..34af4ddc 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -1,3 +1,5 @@ +from ConfigParser import SafeConfigParser + from redeem.CascadingConfigParser import CascadingConfigParser from redeem.configuration.RedeemConfig import RedeemConfig from redeem.configuration.sections.alarms import AlarmsConfig @@ -19,53 +21,75 @@ from redeem.configuration.sections.system import SystemConfig from redeem.configuration.sections.watchdog import WatchdogConfig +import logging + + +def _clean(key): + return key.replace('-', '_').lower() + class ConfigFactory(object): - cpp = None - sections = [ - ('Alarms', AlarmsConfig), - ('Cold-ends', ColdendsConfig), - ('Delta', DeltaConfig), - ('Endstops', EndstopsConfig), - ('Fans', FansConfig), - ('Filament-sensors', FilamentSensorsConfig), - ('Geometry', GeometryConfig), - ('Heaters', HeatersConfig), - ('Homing', HomingConfig), - ('Macros', MacrosConfig), - ('Planner', PlannerConfig), - ('HPX2MaxPlugin', HPX2MaxPluginConfig), - ('DualServoPlugin', DualServoPluginConfig), - ('Probe', ProbeConfig), - ('Rotary-encoders', RotaryEncodersConfig), - ('Servos', ServosConfig), - ('Steppers', SteppersConfig), - ('System', SystemConfig), - ('Watchdog', WatchdogConfig) - ] - def __init__(self, config_files): - self.ccp = CascadingConfigParser(config_files) + sections = { + 'Alarms': AlarmsConfig, + 'Cold-ends': ColdendsConfig, + 'Delta': DeltaConfig, + 'Endstops': EndstopsConfig, + 'Fans': FansConfig, + 'Filament-sensors': FilamentSensorsConfig, + 'Geometry': GeometryConfig, + 'Heaters': HeatersConfig, + 'Homing': HomingConfig, + 'Macros': MacrosConfig, + 'Planner': PlannerConfig, + 'HPX2MaxPlugin': HPX2MaxPluginConfig, + 'DualServoPlugin': DualServoPluginConfig, + 'Probe': ProbeConfig, + 'Rotary-encoders': RotaryEncodersConfig, + 'Servos': ServosConfig, + 'Steppers': SteppersConfig, + 'System': SystemConfig, + 'Watchdog': WatchdogConfig + } + + def __init__(self): + pass - def hydrate_config(self): + def hydrate_config(self, config_file=None, config_files=()): """Use default mapper, unless another one is specified by subclass""" + config_parser = SafeConfigParser() + + if config_file is not None and len(config_files) > 0: + raise Exception("cannot provide both single and list of config files") + + if config_file: + config_parser.read([config_file, ]) + elif len(config_files) > 0: + config_parser.read(config_files) + redeem_config = RedeemConfig() - for section_name, section_cls in self.sections: + + for section in config_parser.sections(): + if section not in self.sections.keys(): + logging.warn("[{}] does not match known section".format(section)) + continue + section_cls = self.sections[section] hydration_name = 'hydrate_' + section_cls.__name__.lower() if hasattr(self, hydration_name): config_func = getattr(self, hydration_name) config = config_func() else: - config = self.hydrate_section_config(section_name, section_cls) - setattr(redeem_config, section_name.lower(), config) + config = self.hydrate_section_config(config_parser, section, section_cls) + assert(hasattr(redeem_config, _clean(section))) + setattr(redeem_config, _clean(section), config) return redeem_config - def hydrate_section_config(self, section_name, config_cls): + def hydrate_section_config(self, config_parser, section, config_cls): """A simple one-to-one mapper from ini to config class""" config = config_cls() - for item in config_cls.__dict__: - if item.startswith('__'): + for option in config_parser.options(section): + if not config.has(option): + logging.warn("[{}] '{}' does not match known option".format(section, option)) continue - item = item.replace('_', '-') - setattr(config, item, self.ccp.get(section_name, item)) + setattr(config, option, config_parser.get(section, option)) return config diff --git a/configs/default.cfg b/tests/core/resources/default.1.9.cfg similarity index 94% rename from configs/default.cfg rename to tests/core/resources/default.1.9.cfg index c51838dc..d1b36681 100644 --- a/configs/default.cfg +++ b/tests/core/resources/default.1.9.cfg @@ -53,25 +53,27 @@ offset_c = 0.0 bed_compensation_matrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]] [Delta] +# Distance head extends below the effector. +Hez = 0.0 # Length of the rod L = 0.135 # Radius of the columns r = 0.144 - -# Compensation for positional error of the columns - -# Radial offsets of the columns -# Positive values move the tower away from the center of the printer +# Effector offset +Ae = 0.026 +Be = 0.026 +Ce = 0.026 +# Carriage offset A_radial = 0.0 B_radial = 0.0 C_radial = 0.0 -# Angular offsets of the columns -# Positive values move the tower counter-clockwise, as seen from above -# Specified in degrees -A_angular = 0.0 -B_angular = 0.0 -C_angular = 0.0 +# Compensation for positional error of the columns +# (For details, read: https://github.com/hercek/Marlin/blob/Marlin_v1/calibration.wxm) +# Positive values move the tower to the right, in the +X direction, tangent to it's radius +A_tangential = 0.0 +B_tangential = 0.0 +C_tangential = 0.0 # Stepper e is ext 1, h is ext 2 [Steppers] @@ -174,9 +176,15 @@ move_cache_size = 1024 # time to wait for buffer to fill, (ms) print_move_buffer_wait = 250 +# if total buffered time gets below (min_buffered_move_time) then wait for (print_move_buffer_wait) before moving again, (ms) +min_buffered_move_time = 100 + # total buffered move time should not exceed this much (ms) max_buffered_move_time = 1000 +# max segment length +max_length = 0.001 + acceleration_x = 0.5 acceleration_y = 0.5 acceleration_z = 0.5 @@ -205,8 +213,15 @@ max_speed_a = 0.2 max_speed_b = 0.2 max_speed_c = 0.2 -# for arc commands, seperate into segments of length in m -arc_segment_length = 0.001 +# Max speed for the steppers in m/s +min_speed_x = 0.005 +min_speed_y = 0.005 +min_speed_z = 0.005 +min_speed_e = 0.01 +min_speed_h = 0.01 +min_speed_a = 0.01 +min_speed_b = 0.01 +min_speed_c = 0.01 # When true, movements on the E axis (eg, G1, G92) will apply # to the active tool (similar to other firmwares). When false, @@ -265,13 +280,6 @@ default-fan-0-value = 0.0 default-fan-1-value = 0.0 default-fan-2-value = 0.0 default-fan-3-value = 0.0 -default-fan-3-value = 0.0 -default-fan-4-value = 0.0 -default-fan-5-value = 0.0 -default-fan-6-value = 0.0 -default-fan-7-value = 0.0 -default-fan-8-value = 0.0 -default-fan-9-value = 0.0 [Heaters] # For list of available temp charts, look in temp_chart.py @@ -400,8 +408,6 @@ pin_Y2 = GPIO3_17 pin_Z1 = GPIO0_31 pin_Z2 = GPIO0_4 -# On A4A you'll need pin_Y2 = GPIO1_19 - keycode_X1 = 112 keycode_X2 = 113 keycode_Y1 = 114 @@ -457,9 +463,6 @@ soft_end_stop_max_c = 1000.0 [Homing] -# default G28 homing axes -G28_default_axes = X,Y,Z,E,H,A,B,C - # Homing speed for the steppers in m/s # Search to minimum ends by default. Negative value for searching to maximum ends. home_speed_x = 0.1 @@ -544,9 +547,6 @@ alarm-level-e = 0.01 [Watchdog] enable_watchdog = True -[Alarms] - - [Macros] G29 = M561 ; Reset the bed level matrix diff --git a/tests/core/resources/local.cfg b/tests/core/resources/local.cfg new file mode 100644 index 00000000..e1ffd4f8 --- /dev/null +++ b/tests/core/resources/local.cfg @@ -0,0 +1,2 @@ +[System] +loglevel = 30 diff --git a/tests/core/resources/printer.cfg b/tests/core/resources/printer.cfg new file mode 100644 index 00000000..37f5e7d9 --- /dev/null +++ b/tests/core/resources/printer.cfg @@ -0,0 +1,187 @@ + +[System] + +machine_type = Roskock_Max_v2 + +[Geometry] +# Delta +axis_config = 3 + +# Set the total length each axis can travel +travel_x = -0.6 +travel_y = -0.6 +travel_z = -0.6 + +# Define the origin in relation to the endstops +# Starting point befor calibration. +offset_x = -0.381 +offset_y = -0.381 +offset_z = -0.381 + +[Delta] +# Length of the rod +l = 0.2908 +# Radius of the columns +#Increase to lower nozzle in relation to outer measurements. +#Decrease to raise nozzle in relation to outer measurements. +r = 0.12685 + +# Carriage offset (the distance from the column to the carriage's center of the rods' joints) +#A_radial = 0.0384 +#B_radial = 0.0384 +#C_radial = 0.0384 + +# Stepper e is ext 1, h is ext 2 +[Steppers] +current_x = 0.735 +current_y = 0.735 +current_z = 0.735 +current_e = 0.650 +current_h = 0.650 + +steps_pr_mm_x = 5.0 +steps_pr_mm_y = 5.0 +steps_pr_mm_z = 5.0 +steps_pr_mm_e = 5.79 +steps_pr_mm_h = 5.79 + +# Which steppers are enabled +in_use_x = True +in_use_y = True +in_use_z = True +in_use_e = True + +slow_decay_x = 1 +slow_decay_y = 1 +slow_decay_z = 1 +slow_decay_e = 1 +slow_decay_h = 1 + +microstepping_x = 6 +microstepping_y = 6 +microstepping_z = 6 +microstepping_e = 6 + +[Heaters] +# For list of available temp charts, look in temp_chart.py + +# E3D v6 Hot end +temp_chart_E = SEMITEC-104GT-2 +pid_p_E = 0.1 +pid_i_E = 0.01 +pid_d_E = 0.3 +ok_range_E = 4.0 + +#Heated Bed +temp_chart_HBP = SEMITEC-104GT-2 +pid_p_HBP = 0.1 +pid_i_HBP = 0.01 +pid_d_HBP = 0.9 +ok_range_HBP = 4.0 + +[Endstops] + +end_stop_X1_stops = x_ccw +end_stop_Y1_stops = y_ccw +end_stop_Z1_stops = z_ccw + +soft_end_stop_min_x = -0.05 +soft_end_stop_min_y = -0.05 +soft_end_stop_min_z = -0.001 + +soft_end_stop_max_x = 0.05 +soft_end_stop_max_y = 0.05 +soft_end_stop_max_z = 0.6 + +has_x = True +has_y = True +has_z = True + +# Invert = +# True means endstop is connected as Normally Open (NO) or not connected +# False means endstop is connected as Normally Closed (NC)invert_X1 = True +invert_Y1 = True +invert_Z1 = True +invert_X2 = True +invert_Y2 = True +invert_Z2 = True + +[Homing] +#Where the ptinter should go for homing +home_x = 0 +home_y = 0 +home_z = 370 +home_e = 0.0 + +#Homing speed for the steppers in M/s +home_speed_x = 0.150 +home_speed_y = 0.150 +home_speed_z = 0.150 + +#Homing backoff speed +home_backoff_speed_x = 0.025 +home_backoff_speed_y = 0.025 +home_backoff_speed_z = 0.025 + +# homing backoff dist +home_backoff_offset_x = 0.01 +home_backoff_offset_y = 0.01 +home_backoff_offset_z = 0.01 + +[Cold-ends] +# We want the E3D fan to turn on when the thermistor goes above 50 + +connect-therm-E-fan-1 = True +add-fan-0-to-M106 = True +add-fan-3-to-M106 = True + +# If you want coolers to +# have a different 'keep' temp, list it here. +cooler_0_target_temp = 50 + +[Planner] +# Max speed for the steppers in m/s +max_speed_x = 0.4 +max_speed_y = 0.4 +max_speed_z = 0.4 +max_speed_e = 0.4 +max_speed_h = 0.4 + +#Max Acceleration for Printing moves M/s^2 (Taken from Repeiter.confg) +#acceleration_x = 1.850 +#acceleration_y = 1.850 +#acceleration_z = 1.850 + +[Probe] +offset_x = 0.0 +offset_y = 0.0 + +[Macros] +#Copied form kossel_mini.cfg I never tried it on a RostockMax +g29 = + M557 P0 X0 Y0 Z5 + M557 P1 X50 Y0 Z5 ; Set probe point + M557 P2 X0 Y50 Z5 ; Set probe point + M557 P3 X-50 Y0 Z5 ; Set probe point + M557 P4 X0 Y-40 Z5 ; Set probe point + M557 P5 X25 Y0 Z5 + M557 P6 X0 Y25 Z5 + M557 P7 X-25 Y0 Z5 + M557 P8 X0 Y-25 Z5 + G32 ; Undock probe + G28 ; Home steppers + G30 P0 S + G30 P1 S ; Probe point 1 + G30 P2 S ; Probe point 2 + G30 P3 S ; Probe point 3 + G30 P4 S ; Probe point 4 + G30 P5 S + G30 P6 S + G30 P7 S + G30 P8 S + G31 ; Dock probe +G32 = + M106 P2 S255 ; Turn on power to probe. + +G31 = + M106 P2 S0 ; Turn off power to probe. diff --git a/tests/core/test_configs.py b/tests/core/test_configs.py index e89b04ee..20112134 100644 --- a/tests/core/test_configs.py +++ b/tests/core/test_configs.py @@ -3,8 +3,11 @@ import os +import re + from redeem.configuration.RedeemConfig import RedeemConfig from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 +from tests.logger_test import LogTestCase current_path = os.path.dirname(os.path.abspath(__file__)) @@ -35,14 +38,44 @@ def test_all_keys_in_config(self): def test_basic_config_factory(self): - files = [ - os.path.join(current_path, 'resources/default.2.0.cfg'), - ] - - factory = ConfigFactoryV20(files) - redeem_config = factory.hydrate_config() + factory = ConfigFactoryV20() + redeem_config = factory.hydrate_config(config_file=os.path.join(current_path, 'resources/default.2.0.cfg')) default_cfg = os.path.join(current_path, 'resources/default.2.0.cfg') cp = RawConfigParser() cp.read(default_cfg) self._compare_configs(cp, redeem_config) + + +class ConfigWarningTests(LogTestCase): + + known_19_mismatches = ('hez', 'ae', 'be', 'ce', 'a_tangential', 'b_tangential', 'c_tangential', 'min_buffered_move_time', 'max_length', 'min_speed_x', 'min_speed_y', 'min_speed_z', 'min_speed_e', 'min_speed_h', 'min_speed_a', 'min_speed_b', 'min_speed_c',) + + def test_old_config_factory(self): + """ test to make sure that 1.9 config mismatches the above list""" + + factory = ConfigFactoryV20() + + with self.assertLogs('', level='WARN') as cm: + factory.hydrate_config(config_file=os.path.join(current_path, 'resources/default.1.9.cfg')) + for warning in cm.output: + m = re.search(r'.*?\'([a-z_]+)\'', warning) + self.assertIsNotNone(m, "needs to match: {}".format(warning)) + self.assertIn(m.group(1), self.known_19_mismatches) + + +class LoadMultipleConfigs(LogTestCase): + + def test_printer_and_local(self): + + files = [ + os.path.join(current_path, 'resources/printer.cfg'), + os.path.join(current_path, 'resources/local.cfg') + ] + + factory = ConfigFactoryV20() + redeem_config = factory.hydrate_config(config_files=files) + + # make sure local takes precedence + self.assertEqual(redeem_config.getint('System', 'loglevel'), 30) + diff --git a/tests/logger_test.py b/tests/logger_test.py new file mode 100644 index 00000000..7fb2e3c7 --- /dev/null +++ b/tests/logger_test.py @@ -0,0 +1,105 @@ +# logger_test.py +# this file contains the base class containing the newly added method +# assertLogs +import collections +import logging +import unittest + +_LoggingWatcher = collections.namedtuple("_LoggingWatcher", + ["records", "output"]) + +class _BaseTestCaseContext(object): + + def __init__(self, test_case): + self.test_case = test_case + + def _raiseFailure(self, standardMsg): + msg = self.test_case._formatMessage(self.msg, standardMsg) + raise self.test_case.failureException(msg) + + +class _CapturingHandler(logging.Handler): + """ + A logging handler capturing all (raw and formatted) logging output. + """ + + def __init__(self): + logging.Handler.__init__(self) + self.watcher = _LoggingWatcher([], []) + + def flush(self): + pass + + def emit(self, record): + self.watcher.records.append(record) + msg = self.format(record) + self.watcher.output.append(msg) + + +class _AssertLogsContext(_BaseTestCaseContext): + """A context manager used to implement TestCase.assertLogs().""" + + LOGGING_FORMAT = "%(levelname)s:%(name)s:%(message)s" + + def __init__(self, test_case, logger_name, level): + _BaseTestCaseContext.__init__(self, test_case) + self.logger_name = logger_name + if level: + self.level = logging._levelNames.get(level, level) + else: + self.level = logging.INFO + self.msg = None + + def __enter__(self): + if isinstance(self.logger_name, logging.Logger): + logger = self.logger = self.logger_name + else: + logger = self.logger = logging.getLogger(self.logger_name) + formatter = logging.Formatter(self.LOGGING_FORMAT) + handler = _CapturingHandler() + handler.setFormatter(formatter) + self.watcher = handler.watcher + self.old_handlers = logger.handlers[:] + self.old_level = logger.level + self.old_propagate = logger.propagate + logger.handlers = [handler] + logger.setLevel(self.level) + logger.propagate = False + return handler.watcher + + def __exit__(self, exc_type, exc_value, tb): + self.logger.handlers = self.old_handlers + self.logger.propagate = self.old_propagate + self.logger.setLevel(self.old_level) + if exc_type is not None: + # let unexpected exceptions pass through + return False + if len(self.watcher.records) == 0: + self._raiseFailure( + "no logs of level {} or higher triggered on {}" + .format(logging.getLevelName(self.level), self.logger.name)) + + +class LogTestCase(unittest.TestCase): + + def assertLogs(self, logger=None, level=None): + """Fail unless a log message of level *level* or higher is emitted + on *logger_name* or its children. If omitted, *level* defaults to + INFO and *logger* defaults to the root logger. + + This method must be used as a context manager, and will yield + a recording object with two attributes: `output` and `records`. + At the end of the context manager, the `output` attribute will + be a list of the matching formatted log messages and the + `records` attribute will be a list of the corresponding LogRecord + objects. + + Example:: + + with self.assertLogs('foo', level='INFO') as cm: + logging.getLogger('foo').info('first message') + logging.getLogger('foo.bar').error('second message') + self.assertEqual(cm.output, ['INFO:foo:first message', + 'ERROR:foo.bar:second message']) + """ + return _AssertLogsContext(self, logger, level) From 0f037b10e2d11511ce06e50ed93aaae9ec62ed9b Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Sat, 28 Oct 2017 23:24:06 -0400 Subject: [PATCH 05/14] build out 1.9 transformations for delta config --- .../factories/ConfigFactoryV19.py | 152 +++++++++++++++++- redeem/configuration/factories/__init__.py | 2 +- 2 files changed, 152 insertions(+), 2 deletions(-) diff --git a/redeem/configuration/factories/ConfigFactoryV19.py b/redeem/configuration/factories/ConfigFactoryV19.py index 0decf264..ee74225d 100644 --- a/redeem/configuration/factories/ConfigFactoryV19.py +++ b/redeem/configuration/factories/ConfigFactoryV19.py @@ -1,5 +1,155 @@ +import numpy as np + from redeem.configuration.factories import ConfigFactory +from redeem.configuration.sections.delta import DeltaConfig + + +def _getfloat(config_parser, section, option, default): + if config_parser.has_option(section, option): + return config_parser.getfloat(section, option) + return default + +def _degreesToRadians(radians): + return radians * 180 / np.pi class ConfigFactoryV19(ConfigFactory): - pass + + def _calc_old_column_position(self, r, + ae, be, ce, + a_tangential, b_tangential, c_tangential, + a_radial, b_radial, c_radial): + """from https://github.com/intelligent-agent/redeem/blob/5f225ddf3ab806ef8996e1431bbef6c454a60f48/redeem/path_planner/Delta.cpp""" # noqa + + # Column theta + At = np.pi / 2.0 + Bt = 7.0 * np.pi / 6.0 + Ct = 11.0 * np.pi / 6.0 + + # Calculate the column tangential offsets + Apxe = a_tangential # Tower A doesn't require a separate y componen + Apye = 0.00 + Bpxe = b_tangential / 2.0 + Bpye = np.sqrt(3.0)*(-b_tangential/2.0) + Cpxe = np.sqrt(3.0)*(c_tangential/2.0) + Cpye = c_tangential/2.0 + + # Calculate the column positions + Apx = (a_radial + r) * np.cos(At) + Apxe + Apy = (a_radial + r) * np.sin(At) + Apye + Bpx = (b_radial + r) * np.cos(Bt) + Bpxe + Bpy = (b_radial + r) * np.sin(Bt) + Bpye + Cpx = (c_radial + r) * np.cos(Ct) + Cpxe + Cpy = (c_radial + r) * np.sin(Ct) + Cpye + + # Calculate the effector positions + Aex = ae * np.cos(At) + Aey = ae * np.sin(At) + Bex = be * np.cos(Bt) + Bey = be * np.sin(Bt) + Cex = ce * np.cos(Ct) + Cey = ce * np.sin(Ct) + + # Calculate the virtual column positions + Avx = Apx - Aex + Avy = Apy - Aey + Bvx = Bpx - Bex + Bvy = Bpy - Bey + Cvx = Cpx - Cex + Cvy = Cpy - Cey + + return Avx, Avy, Bvx, Bvy, Cvx, Cvy + + def _calc_new_column_position(self, r, Avx, Avy, Bvx, Bvy, Cvx, Cvy): + """ + from new calcs + + + 1) Avx = (A_radial + r)*cos(At); + 2) Avy = (A_radial + r)*sin(At); + + solve for a_radial + 3) (Avx / cos(At)) - r = A_radial + 4) (Avy / sin(At)) - r = A_radial + + set 3 equal to 4 + 5) Avy / Avx = sin(At) / cos(At) = tan(At) + + solve for At + 6) arctan(Avy/Avx) = At + + a_radial from 6 into 1 + """ + + At = np.arctan(Avy / Avx) + Bt = np.arctan(Bvy / Bvx) + Ct = np.arctan(Cvy / Cvx) + + a_radial = (Avx / np.cos(At)) - r + b_radial = (Bvx / np.cos(Bt)) - r + c_radial = (Cvx / np.cos(Ct)) - r + + '''from new calcs + At = degreesToRadians(90.0 + A_angular) + Bt = degreesToRadians(210.0 + B_angular) + Ct = degreesToRadians(330.0 + C_angular)''' + + # solve for _angular + a_angular = _degreesToRadians(At) - 90 + b_angular = _degreesToRadians(Bt) - 210 + c_angular = _degreesToRadians(Ct) - 330 + + return a_radial, b_radial, c_radial, a_angular, b_angular, c_angular + + def hydrate_deltaconfig(self, config_parser): + """ + + 1.9 used ae, be, ce along with a/b/c_tangential to calculate position of each tower + + 2.0 users angular and radial dimensions + + + also r in 1.9 was just radius to edge of effector, instead of the center as in 2.0 + + """ + cfg = DeltaConfig() + + # if this isn't a delta config, skip transformations + if config_parser.getint('geometry', 'axis_config') != 3: + return cfg + + + # length of rod same + if config_parser.has_option('delta', 'l'): + cfg.l = config_parser.getfloat('delta', 'l') + + # radius2.0 = radius1.9 + Ae + if config_parser.has_option('delta', 'r'): + cfg.r = config_parser.getfloat('delta', 'r') + + if config_parser.has_option('delta', 'ae'): + cfg.r += config_parser.getfloat('delta', 'ae') + + r = _getfloat(config_parser, 'delta', 'r', 0.0) + ae = _getfloat(config_parser, 'delta', 'ae', 0.0) + be = _getfloat(config_parser, 'delta', 'be', 0.0) + ce = _getfloat(config_parser, 'delta', 'ce', 0.0) + a_radial = _getfloat(config_parser, 'delta', 'a_radial', 0.0) + b_radial = _getfloat(config_parser, 'delta', 'b_radial', 0.0) + c_radial = _getfloat(config_parser, 'delta', 'c_radial', 0.0) + a_tangential = _getfloat(config_parser, 'delta', 'a_tangential', 0.0) + b_tangential = _getfloat(config_parser, 'delta', 'b_tangential', 0.0) + c_tangential = _getfloat(config_parser, 'delta', 'c_tangential', 0.0) + + Avx, Avy, Bvx, Bvy, CvX, Cvy = self._calc_old_column_position(r, + ae, be, ce, + a_tangential, b_tangential, c_tangential, + a_radial, b_radial, c_radial) + + (cfg.a_radial, cfg.b_radial, cfg.c_radial, + cfg.a_angular, cfg.b_angular, cfg.c_angular) = self._calc_new_column_position(cfg.r, + Avx, Avy, + Bvx, Bvy, + CvX, Cvy) + + return cfg \ No newline at end of file diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index 34af4ddc..449cd0c1 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -77,7 +77,7 @@ def hydrate_config(self, config_file=None, config_files=()): hydration_name = 'hydrate_' + section_cls.__name__.lower() if hasattr(self, hydration_name): config_func = getattr(self, hydration_name) - config = config_func() + config = config_func(config_parser) else: config = self.hydrate_section_config(config_parser, section, section_cls) assert(hasattr(redeem_config, _clean(section))) From a3600159ba19561f8543eadc3ad2f3449239433a Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Mon, 30 Oct 2017 22:04:07 -0400 Subject: [PATCH 06/14] added config versions to printer files. added tests for config v1.9 conversion. updated Redeem class to handle config factories --- configs/Kossel_K25000.cfg | 1 + configs/debrew.cfg | 4 + configs/kossel_mini.cfg | 1 + configs/makerbot_cupcake.cfg | 1 + configs/maxcorexy.cfg | 2 + configs/mendelmax.cfg | 2 + configs/prusa_i3.cfg | 1 + configs/prusa_i3_quad.cfg | 1 + configs/rostock_max_v2.cfg | 1 + configs/series1.cfg | 1 + configs/testing_rev_A.cfg | 1 + configs/testing_rev_B.cfg | 1 + configs/thing.cfg | 2 + configs/thing_delta.cfg | 1 + configs/ultimaker_original.cfg | 4 + redeem/Redeem.py | 16 +- redeem/configuration/RedeemConfig.py | 43 ++++ redeem/configuration/__init__.py | 20 ++ .../factories/ConfigFactoryV19.py | 64 +++--- redeem/configuration/factories/__init__.py | 4 +- redeem/configuration/sections/__init__.py | 5 + tests/core/resources/delta_local1.9.cfg | 24 ++ tests/core/resources/delta_printer1.9.cfg | 205 ++++++++++++++++++ tests/core/test_configs.py | 52 +++++ 24 files changed, 417 insertions(+), 40 deletions(-) create mode 100644 tests/core/resources/delta_local1.9.cfg create mode 100644 tests/core/resources/delta_printer1.9.cfg diff --git a/configs/Kossel_K25000.cfg b/configs/Kossel_K25000.cfg index ca46c75c..5f1a371d 100644 --- a/configs/Kossel_K25000.cfg +++ b/configs/Kossel_K25000.cfg @@ -1,6 +1,7 @@ [System] machine_type = Kossel_K25000 +version = 2.0 [Geometry] # Delta diff --git a/configs/debrew.cfg b/configs/debrew.cfg index d33acc42..3701f436 100644 --- a/configs/debrew.cfg +++ b/configs/debrew.cfg @@ -1,3 +1,7 @@ +[System] + +version = 2.0 + [Geometry] # Delta axis_config = 3 diff --git a/configs/kossel_mini.cfg b/configs/kossel_mini.cfg index 05b29a1a..4507934b 100644 --- a/configs/kossel_mini.cfg +++ b/configs/kossel_mini.cfg @@ -1,6 +1,7 @@ [System] machine_type = Kossel_mini +version = 2.0 [Geometry] # Delta diff --git a/configs/makerbot_cupcake.cfg b/configs/makerbot_cupcake.cfg index bc952136..c6825565 100644 --- a/configs/makerbot_cupcake.cfg +++ b/configs/makerbot_cupcake.cfg @@ -1,6 +1,7 @@ [System] machine_type = Makerbot_Cupcake +version = 2.0 [Geometry] # 0 - Cartesian diff --git a/configs/maxcorexy.cfg b/configs/maxcorexy.cfg index 120ab533..cc21eed1 100644 --- a/configs/maxcorexy.cfg +++ b/configs/maxcorexy.cfg @@ -1,5 +1,7 @@ [System] +version = 2.0 + [Geometry] # Core-XY axis_config = 2 diff --git a/configs/mendelmax.cfg b/configs/mendelmax.cfg index c0524896..4941de6a 100644 --- a/configs/mendelmax.cfg +++ b/configs/mendelmax.cfg @@ -1,5 +1,7 @@ [System] +version = 2.0 + [Geometry] # Cartesian XY axis_config = 0 diff --git a/configs/prusa_i3.cfg b/configs/prusa_i3.cfg index 0f5762c2..5c425443 100644 --- a/configs/prusa_i3.cfg +++ b/configs/prusa_i3.cfg @@ -1,6 +1,7 @@ [System] machine_type = Prusa_I3 +version = 2.0 [Geometry] offset_x = -0.19 diff --git a/configs/prusa_i3_quad.cfg b/configs/prusa_i3_quad.cfg index 6e656cbd..1547dd77 100644 --- a/configs/prusa_i3_quad.cfg +++ b/configs/prusa_i3_quad.cfg @@ -1,6 +1,7 @@ [System] machine_type = Prusa_I3 +version = 2.0 [Geometry] offset_x = -0.19 diff --git a/configs/rostock_max_v2.cfg b/configs/rostock_max_v2.cfg index 37f5e7d9..b64a69ee 100644 --- a/configs/rostock_max_v2.cfg +++ b/configs/rostock_max_v2.cfg @@ -2,6 +2,7 @@ [System] machine_type = Roskock_Max_v2 +version = 2.0 [Geometry] # Delta diff --git a/configs/series1.cfg b/configs/series1.cfg index 23c77d20..d54d7903 100644 --- a/configs/series1.cfg +++ b/configs/series1.cfg @@ -8,6 +8,7 @@ loglevel = 10 # Machine type is used by M115 # to identify the machine connected. machine_type = Series 1 Pro +version = 2.0 [Geometry] diff --git a/configs/testing_rev_A.cfg b/configs/testing_rev_A.cfg index 2533b449..974f9ef6 100644 --- a/configs/testing_rev_A.cfg +++ b/configs/testing_rev_A.cfg @@ -3,6 +3,7 @@ [System] loglevel = 10 +version = 2.0 [Geometry] axis_config = 0 diff --git a/configs/testing_rev_B.cfg b/configs/testing_rev_B.cfg index 13412c79..0ea90135 100644 --- a/configs/testing_rev_B.cfg +++ b/configs/testing_rev_B.cfg @@ -3,6 +3,7 @@ [System] loglevel = 10 +version = 2.0 [Geometry] axis_config = 0 diff --git a/configs/thing.cfg b/configs/thing.cfg index e7c3fe4c..bf0693ea 100644 --- a/configs/thing.cfg +++ b/configs/thing.cfg @@ -1,5 +1,7 @@ [System] +version = 2.0 + [Geometry] # Thing has H-belt axis_config = 1 diff --git a/configs/thing_delta.cfg b/configs/thing_delta.cfg index d46dbbd1..5ecdbcd1 100644 --- a/configs/thing_delta.cfg +++ b/configs/thing_delta.cfg @@ -1,6 +1,7 @@ [System] machine_type = Thing delta +version = 2.0 [Geometry] # Delta diff --git a/configs/ultimaker_original.cfg b/configs/ultimaker_original.cfg index 295863d9..3d60d69c 100644 --- a/configs/ultimaker_original.cfg +++ b/configs/ultimaker_original.cfg @@ -1,3 +1,7 @@ +[System] + +version = 2.0 + [Geometry] travel_x = 0.2 travel_y = -0.2 diff --git a/redeem/Redeem.py b/redeem/Redeem.py index 63894caf..bbfac165 100755 --- a/redeem/Redeem.py +++ b/redeem/Redeem.py @@ -67,6 +67,7 @@ from Watchdog import Watchdog # Global vars +from redeem.configuration import get_config_factory from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 printer = None @@ -106,14 +107,13 @@ def __init__(self, config_location="/etc/redeem"): os.chmod(local_path, 0o777) # Parse the config files. - config_factory = ConfigFactoryV20([os.path.join(config_location, 'printer.cfg'), os.path.join(config_location,'local.cfg')]) - printer.config = config_factory.hydrate_config() - - # 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')) + config_files = [ + os.path.join(config_location, 'printer.cfg'), + os.path.join(config_location, 'local.cfg') + ] + + config_factory = get_config_factory() + printer.config = config_factory.hydrate_config(config_files) # Get the revision and loglevel from the Config file level = self.printer.config.getint('System', 'loglevel') diff --git a/redeem/configuration/RedeemConfig.py b/redeem/configuration/RedeemConfig.py index 051bef1d..05a49d57 100644 --- a/redeem/configuration/RedeemConfig.py +++ b/redeem/configuration/RedeemConfig.py @@ -43,6 +43,10 @@ class RedeemConfig(object): system = SystemConfig() watchdog = WatchdogConfig() + replicape_revision = None + replicape_data = None + replicape_path = None + def get(self, section, key): if hasattr(self, section.replace('-', '_').lower()): return getattr(self, section.replace('-', '_').lower()).get(key) @@ -51,6 +55,10 @@ def get(self, section, key): def has(self, section, key): return hasattr(self, section.replace('-', '_').lower()) and getattr(self, section.replace('-', '_').lower()).has(key) + # alias + def has_option(self, section, key): + return self.has(section, key) + def getint(self, section, key): if hasattr(self, section.replace('-', '_').lower()): return getattr(self, section.replace('-', '_').lower()).getint(key) @@ -60,3 +68,38 @@ def getfloat(self, section, key): if hasattr(self, section.replace('-', '_').lower()): return getattr(self, section.replace('-', '_').lower()).getfloat(key) return None + + def getboolean(self, section, key): + if hasattr(self, section.replace('-','_').lower()): + return getattr(self, section.replace('-', '_').lower()).getboolean(key) + return False + + def parse_capes(self): + """ Read the name and revision of each cape on the BeagleBone """ + self.replicape_revision = None + self.reach_revision = None + + import glob + paths = glob.glob("/sys/bus/i2c/devices/[1-2]-005[4-7]/*/nvmem") + paths.extend(glob.glob("/sys/bus/i2c/devices/[1-2]-005[4-7]/nvmem/at24-[1-4]/nvmem")) + # paths.append(glob.glob("/sys/bus/i2c/devices/[1-2]-005[4-7]/eeprom")) + for i, path in enumerate(paths): + try: + with open(path, "rb") as f: + data = f.read(120) + name = data[58:74].strip() + if name == "BB-BONE-REPLICAP": + self.replicape_revision = data[38:42] + self.replicape_data = data + self.replicape_path = path + elif name[:13] == "BB-BONE-REACH": + self.reach_revision = data[38:42] + self.reach_data = data + self.reach_path = path + if self.replicape_revision != None and self.reach_revision != None: + break + except IOError as e: + pass + + def save(self, filename): + raise NotImplemented("not yet implemented") diff --git a/redeem/configuration/__init__.py b/redeem/configuration/__init__.py index e69de29b..d11f8ed7 100644 --- a/redeem/configuration/__init__.py +++ b/redeem/configuration/__init__.py @@ -0,0 +1,20 @@ +from ConfigParser import SafeConfigParser + +from redeem.configuration.factories.ConfigFactoryV19 import ConfigFactoryV19 +from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 + + +def get_config_factory(config_files): + + config_parser = SafeConfigParser() + config_parser.read(config_files) + + version = None + if config_parser.has_option('System', 'version'): + version = config_parser.get('System', 'version') + + factory = ConfigFactoryV19() + if version == 2.0: + factory = ConfigFactoryV20() + + return factory diff --git a/redeem/configuration/factories/ConfigFactoryV19.py b/redeem/configuration/factories/ConfigFactoryV19.py index ee74225d..422b6578 100644 --- a/redeem/configuration/factories/ConfigFactoryV19.py +++ b/redeem/configuration/factories/ConfigFactoryV19.py @@ -9,7 +9,8 @@ def _getfloat(config_parser, section, option, default): return config_parser.getfloat(section, option) return default -def _degreesToRadians(radians): + +def _radiansToDegrees(radians): return radians * 180 / np.pi @@ -73,12 +74,14 @@ def _calc_new_column_position(self, r, Avx, Avy, Bvx, Bvy, Cvx, Cvy): 4) (Avy / sin(At)) - r = A_radial set 3 equal to 4 - 5) Avy / Avx = sin(At) / cos(At) = tan(At) + 5) Avx / cos(At) = Avy / sin(At) + 6) Avx * sin(At) = cos(At) * Avy + 6) Avy / Avx = sin(At) / cos(At) = tan(At) solve for At - 6) arctan(Avy/Avx) = At + 7) arctan(Avy/Avx) = At - a_radial from 6 into 1 + a_radial from 7 into 1 """ At = np.arctan(Avy / Avx) @@ -86,7 +89,7 @@ def _calc_new_column_position(self, r, Avx, Avy, Bvx, Bvy, Cvx, Cvy): Ct = np.arctan(Cvy / Cvx) a_radial = (Avx / np.cos(At)) - r - b_radial = (Bvx / np.cos(Bt)) - r + b_radial = (Bvx / np.cos(Bt)) + r c_radial = (Cvx / np.cos(Ct)) - r '''from new calcs @@ -95,9 +98,9 @@ def _calc_new_column_position(self, r, Avx, Avy, Bvx, Bvy, Cvx, Cvy): Ct = degreesToRadians(330.0 + C_angular)''' # solve for _angular - a_angular = _degreesToRadians(At) - 90 - b_angular = _degreesToRadians(Bt) - 210 - c_angular = _degreesToRadians(Ct) - 330 + a_angular = _radiansToDegrees(At) - 90 + b_angular = _radiansToDegrees(Bt) - 30 + c_angular = _radiansToDegrees(Ct) + 30 return a_radial, b_radial, c_radial, a_angular, b_angular, c_angular @@ -114,32 +117,31 @@ def hydrate_deltaconfig(self, config_parser): """ cfg = DeltaConfig() - # if this isn't a delta config, skip transformations - if config_parser.getint('geometry', 'axis_config') != 3: + # if this isn't a Delta config, skip transformations + if config_parser.getint('Geometry', 'axis_config') != 3: return cfg - - # length of rod same - if config_parser.has_option('delta', 'l'): - cfg.l = config_parser.getfloat('delta', 'l') + # length of rod same in both + if config_parser.has_option('Delta', 'L'): + cfg.l = config_parser.getfloat('Delta', 'L') # radius2.0 = radius1.9 + Ae - if config_parser.has_option('delta', 'r'): - cfg.r = config_parser.getfloat('delta', 'r') - - if config_parser.has_option('delta', 'ae'): - cfg.r += config_parser.getfloat('delta', 'ae') - - r = _getfloat(config_parser, 'delta', 'r', 0.0) - ae = _getfloat(config_parser, 'delta', 'ae', 0.0) - be = _getfloat(config_parser, 'delta', 'be', 0.0) - ce = _getfloat(config_parser, 'delta', 'ce', 0.0) - a_radial = _getfloat(config_parser, 'delta', 'a_radial', 0.0) - b_radial = _getfloat(config_parser, 'delta', 'b_radial', 0.0) - c_radial = _getfloat(config_parser, 'delta', 'c_radial', 0.0) - a_tangential = _getfloat(config_parser, 'delta', 'a_tangential', 0.0) - b_tangential = _getfloat(config_parser, 'delta', 'b_tangential', 0.0) - c_tangential = _getfloat(config_parser, 'delta', 'c_tangential', 0.0) + if config_parser.has_option('Delta', 'r'): + cfg.r = config_parser.getfloat('Delta', 'r') + + if config_parser.has_option('Delta', 'Ae'): + cfg.r -= config_parser.getfloat('Delta', 'Ae') + + r = _getfloat(config_parser, 'Delta', 'r', 0.0) + ae = _getfloat(config_parser, 'Delta', 'Ae', 0.0) + be = _getfloat(config_parser, 'Delta', 'Be', 0.0) + ce = _getfloat(config_parser, 'Delta', 'Ce', 0.0) + a_radial = _getfloat(config_parser, 'Delta', 'A_radial', 0.0) + b_radial = _getfloat(config_parser, 'Delta', 'B_radial', 0.0) + c_radial = _getfloat(config_parser, 'Delta', 'C_radial', 0.0) + a_tangential = _getfloat(config_parser, 'Delta', 'A_tangential', 0.0) + b_tangential = _getfloat(config_parser, 'Delta', 'B_tangential', 0.0) + c_tangential = _getfloat(config_parser, 'Delta', 'C_tangential', 0.0) Avx, Avy, Bvx, Bvy, CvX, Cvy = self._calc_old_column_position(r, ae, be, ce, @@ -152,4 +154,4 @@ def hydrate_deltaconfig(self, config_parser): Bvx, Bvy, CvX, Cvy) - return cfg \ No newline at end of file + return cfg diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index 449cd0c1..c0004b4e 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -65,7 +65,9 @@ def hydrate_config(self, config_file=None, config_files=()): if config_file: config_parser.read([config_file, ]) elif len(config_files) > 0: - config_parser.read(config_files) + num_files = config_parser.read(config_files) + if num_files < len(config_files): + logging.warn("number of files loaded less than provided: {} vs. {}".format(num_files, len(config_files))) redeem_config = RedeemConfig() diff --git a/redeem/configuration/sections/__init__.py b/redeem/configuration/sections/__init__.py index 52858cd7..bbdb4c26 100644 --- a/redeem/configuration/sections/__init__.py +++ b/redeem/configuration/sections/__init__.py @@ -29,3 +29,8 @@ def getint(self, key): except ValueError: return None return val + + def getboolean(self, key): + val = self.get(_clean(key)) + return val in ['True', 'true'] + diff --git a/tests/core/resources/delta_local1.9.cfg b/tests/core/resources/delta_local1.9.cfg new file mode 100644 index 00000000..0a7f355c --- /dev/null +++ b/tests/core/resources/delta_local1.9.cfg @@ -0,0 +1,24 @@ +[Delta] + +Hez = 0.0 + +L = 0.135 + +r = 0.144 + +Ae = 0.026 +Be = 0.026 +Ce = 0.026 + +# Carriage offset +A_radial = 0.001 +B_radial = 0.002 +C_radial = 0.003 + +# Compensation for positional error of the columns +# (For details, read: https://github.com/hercek/Marlin/blob/Marlin_v1/calibration.wxm) +# Positive values move the tower to the right, in the +X direction, tangent to it's radius +A_tangential = 0.004 +B_tangential = 0.005 +C_tangential = 0.006 + diff --git a/tests/core/resources/delta_printer1.9.cfg b/tests/core/resources/delta_printer1.9.cfg new file mode 100644 index 00000000..6f436856 --- /dev/null +++ b/tests/core/resources/delta_printer1.9.cfg @@ -0,0 +1,205 @@ + +[System] + +machine_type = Roskock_Max_v2 + +[Geometry] +# Delta +axis_config = 3 + +# Set the total length each axis can travel +travel_x = -0.6 +travel_y = -0.6 +travel_z = -0.6 + +# Define the origin in relation to the endstops +# Starting point befor calibration. +offset_x = -0.381 +offset_y = -0.381 +offset_z = -0.381 + +[Delta] +# Distance head extends below the effector. +Hez = 0.013 +# Length of the rod +l = 0.2908 +# Radius of the columns +#Increase to lower nozzle in relation to outer measurements. +#Decrease to raise nozzle in relation to outer measurements. +r = 0.19825 + +#Combined Effector and Carriage offset +Ae = 0.0714 +Be = 0.0714 +Ce = 0.0714 + +#Carriage offset set to 0 and added to Effector offset +A_radial = 0 +B_radial = 0 +C_radial = 0 + +#Original Effector and Carriage Numbers +# Effector offset +#Ae = 0.033 +#Be = 0.033 +#Ce = 0.033 + +# Carriage offset (the distance from the column to the carriage's center of the rods' joints) +#A_radial = 0.0384 +#B_radial = 0.0384 +#C_radial = 0.0384 + +# Stepper e is ext 1, h is ext 2 +[Steppers] +current_x = 0.735 +current_y = 0.735 +current_z = 0.735 +current_e = 0.650 +current_h = 0.650 + +steps_pr_mm_x = 5.0 +steps_pr_mm_y = 5.0 +steps_pr_mm_z = 5.0 +steps_pr_mm_e = 5.79 +steps_pr_mm_h = 5.79 + +# Which steppers are enabled +in_use_x = True +in_use_y = True +in_use_z = True +in_use_e = True + +slow_decay_x = 1 +slow_decay_y = 1 +slow_decay_z = 1 +slow_decay_e = 1 +slow_decay_h = 1 + +microstepping_x = 6 +microstepping_y = 6 +microstepping_z = 6 +microstepping_e = 6 + +[Heaters] +# For list of available temp charts, look in temp_chart.py + +# E3D v6 Hot end +temp_chart_E = SEMITEC-104GT-2 +pid_p_E = 0.1 +pid_i_E = 0.01 +pid_d_E = 0.3 +ok_range_E = 4.0 + +#Heated Bed +temp_chart_HBP = SEMITEC-104GT-2 +pid_p_HBP = 0.1 +pid_i_HBP = 0.01 +pid_d_HBP = 0.9 +ok_range_HBP = 4.0 + +[Endstops] + +end_stop_X1_stops = x_ccw +end_stop_Y1_stops = y_ccw +end_stop_Z1_stops = z_ccw + +soft_end_stop_min_x = -0.05 +soft_end_stop_min_y = -0.05 +soft_end_stop_min_z = -0.001 + +soft_end_stop_max_x = 0.05 +soft_end_stop_max_y = 0.05 +soft_end_stop_max_z = 0.6 + +has_x = True +has_y = True +has_z = True + +# Invert = +# True means endstop is connected as Normally Open (NO) or not connected +# False means endstop is connected as Normally Closed (NC)invert_X1 = True +invert_Y1 = True +invert_Z1 = True +invert_X2 = True +invert_Y2 = True +invert_Z2 = True + +[Homing] +#Where the ptinter should go for homing +home_x = 0 +home_y = 0 +home_z = 370 +home_e = 0.0 + +#Homing speed for the steppers in M/s +home_speed_x = 0.150 +home_speed_y = 0.150 +home_speed_z = 0.150 + +#Homing backoff speed +home_backoff_speed_x = 0.025 +home_backoff_speed_y = 0.025 +home_backoff_speed_z = 0.025 + +# homing backoff dist +home_backoff_offset_x = 0.01 +home_backoff_offset_y = 0.01 +home_backoff_offset_z = 0.01 + +[Cold-ends] +# We want the E3D fan to turn on when the thermistor goes above 50 + +connect-therm-E-fan-1 = True +add-fan-0-to-M106 = True +add-fan-3-to-M106 = True + +# If you want coolers to +# have a different 'keep' temp, list it here. +cooler_0_target_temp = 50 + +[Planner] +# Max speed for the steppers in m/s +max_speed_x = 0.4 +max_speed_y = 0.4 +max_speed_z = 0.4 +max_speed_e = 0.4 +max_speed_h = 0.4 + +#Max Acceleration for Printing moves M/s^2 (Taken from Repeiter.confg) +#acceleration_x = 1.850 +#acceleration_y = 1.850 +#acceleration_z = 1.850 + +[Probe] +offset_x = 0.0 +offset_y = 0.0 + +[Macros] +#Copied form kossel_mini.cfg I never tried it on a RostockMax +g29 = + M557 P0 X0 Y0 Z5 + M557 P1 X50 Y0 Z5 ; Set probe point + M557 P2 X0 Y50 Z5 ; Set probe point + M557 P3 X-50 Y0 Z5 ; Set probe point + M557 P4 X0 Y-40 Z5 ; Set probe point + M557 P5 X25 Y0 Z5 + M557 P6 X0 Y25 Z5 + M557 P7 X-25 Y0 Z5 + M557 P8 X0 Y-25 Z5 + G32 ; Undock probe + G28 ; Home steppers + G30 P0 S + G30 P1 S ; Probe point 1 + G30 P2 S ; Probe point 2 + G30 P3 S ; Probe point 3 + G30 P4 S ; Probe point 4 + G30 P5 S + G30 P6 S + G30 P7 S + G30 P8 S + G31 ; Dock probe +G32 = + M106 P2 S255 ; Turn on power to probe. + +G31 = + M106 P2 S0 ; Turn off power to probe. diff --git a/tests/core/test_configs.py b/tests/core/test_configs.py index 20112134..016d3f57 100644 --- a/tests/core/test_configs.py +++ b/tests/core/test_configs.py @@ -6,6 +6,7 @@ import re from redeem.configuration.RedeemConfig import RedeemConfig +from redeem.configuration.factories.ConfigFactoryV19 import ConfigFactoryV19 from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 from tests.logger_test import LogTestCase @@ -64,6 +65,57 @@ def test_old_config_factory(self): self.assertIn(m.group(1), self.known_19_mismatches) +class ConfigV19toV20Tests(LogTestCase): + + def test_delta(self): + """test to make sure delta corrections are zero when tangential and angular are zero""" + + factory = ConfigFactoryV19() + + files = [ + os.path.join(current_path, 'resources/delta_printer1.9.cfg') + ] + + redeem_config = factory.hydrate_config(config_files=files) + + self.assertAlmostEqual(redeem_config.getfloat('delta', 'a_radial'), 0.0) + self.assertAlmostEqual(redeem_config.getfloat('delta', 'a_angular'), 0.0) + + self.assertAlmostEqual(redeem_config.getfloat('delta', 'b_radial'), 0.0) + self.assertAlmostEqual(redeem_config.getfloat('delta', 'b_angular'), 0.0) + + self.assertAlmostEqual(redeem_config.getfloat('delta', 'c_radial'), 0.0) + self.assertAlmostEqual(redeem_config.getfloat('delta', 'c_angular'), 0.0) + + def test_old_config_into_new(self): + """test to make sure delta corrections are zero when tangential and angular are zero""" + + factory = ConfigFactoryV19() + + files = [ + os.path.join(current_path, 'resources/delta_printer1.9.cfg'), + os.path.join(current_path, 'resources/delta_local1.9.cfg') + ] + + redeem_config = factory.hydrate_config(config_files=files) + + # print("a radial : {}".format(redeem_config.getfloat('delta', 'a_radial'))) + # print("a angular : {}".format(redeem_config.getfloat('delta', 'a_angular'))) + # print("b radial : {}".format(redeem_config.getfloat('delta', 'b_radial'))) + # print("b angular : {}".format(redeem_config.getfloat('delta', 'b_angular'))) + # print("c radial : {}".format(redeem_config.getfloat('delta', 'c_radial'))) + # print("c angular : {}".format(redeem_config.getfloat('delta', 'c_angular'))) + + self.assertAlmostEqual(redeem_config.getfloat('delta', 'a_radial'), 0.0010672079121700762) + self.assertAlmostEqual(redeem_config.getfloat('delta', 'a_angular'), -1.9251837083231607) + + self.assertAlmostEqual(redeem_config.getfloat('delta', 'b_radial'), -0.00210412149464) + self.assertAlmostEqual(redeem_config.getfloat('delta', 'b_angular'), 2.38594403039) + + self.assertAlmostEqual(redeem_config.getfloat('delta', 'c_radial'), 0.00610882321576) + self.assertAlmostEqual(redeem_config.getfloat('delta', 'c_angular'), 2.39954455253) + + class LoadMultipleConfigs(LogTestCase): def test_printer_and_local(self): From 6ccb27bd6f79a4fc5e6fbf1ff717b85a8816231f Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Thu, 2 Nov 2017 19:05:11 -0400 Subject: [PATCH 07/14] bug fixing to enable test cases to run. adding default as an option to RedeemConfig accessors. porting get_key from cascadingparser into RedeemConfig --- redeem/Printer.py | 7 +++- redeem/Redeem.py | 4 +-- redeem/configuration/RedeemConfig.py | 54 +++++++++++++++++++++++----- redeem/configuration/__init__.py | 4 +-- 4 files changed, 55 insertions(+), 14 deletions(-) diff --git a/redeem/Printer.py b/redeem/Printer.py index 1f5cc078..689ca4d9 100755 --- a/redeem/Printer.py +++ b/redeem/Printer.py @@ -26,9 +26,10 @@ from Delta import Delta from PruInterface import PruInterface from SDCardManager import SDCardManager -import os import json +from configuration.RedeemConfig import RedeemConfig + class Printer: AXES = "XYZEHABC" @@ -107,6 +108,10 @@ def __init__(self): self.sd_card_manager = SDCardManager() + # default. should be initialized later + # TODO : should be passed into from constructor + self.config = RedeemConfig() + def add_slave(self, master, slave): ''' Make an axis copy the movement of another. the slave will get the same position as the axis''' diff --git a/redeem/Redeem.py b/redeem/Redeem.py index bbfac165..dbd8c31a 100755 --- a/redeem/Redeem.py +++ b/redeem/Redeem.py @@ -112,8 +112,8 @@ def __init__(self, config_location="/etc/redeem"): os.path.join(config_location, 'local.cfg') ] - config_factory = get_config_factory() - printer.config = config_factory.hydrate_config(config_files) + config_factory = get_config_factory(config_files) + printer.config = config_factory.hydrate_config(config_files=config_files) # Get the revision and loglevel from the Config file level = self.printer.config.getint('System', 'loglevel') diff --git a/redeem/configuration/RedeemConfig.py b/redeem/configuration/RedeemConfig.py index 05a49d57..2271816b 100644 --- a/redeem/configuration/RedeemConfig.py +++ b/redeem/configuration/RedeemConfig.py @@ -1,3 +1,8 @@ +import struct +import logging +import random +import string + from redeem.configuration.sections.alarms import AlarmsConfig from redeem.configuration.sections.coldends import ColdendsConfig from redeem.configuration.sections.delta import DeltaConfig @@ -46,11 +51,16 @@ class RedeemConfig(object): replicape_revision = None replicape_data = None replicape_path = None + replicape_key = None + + reach_revision = None + reach_data = None + reach_path = None - def get(self, section, key): + def get(self, section, key, default=None): if hasattr(self, section.replace('-', '_').lower()): return getattr(self, section.replace('-', '_').lower()).get(key) - return None + return default def has(self, section, key): return hasattr(self, section.replace('-', '_').lower()) and getattr(self, section.replace('-', '_').lower()).has(key) @@ -59,20 +69,20 @@ def has(self, section, key): def has_option(self, section, key): return self.has(section, key) - def getint(self, section, key): + def getint(self, section, key, default=None): if hasattr(self, section.replace('-', '_').lower()): return getattr(self, section.replace('-', '_').lower()).getint(key) - return None + return default - def getfloat(self, section, key): + def getfloat(self, section, key, default=None): if hasattr(self, section.replace('-', '_').lower()): return getattr(self, section.replace('-', '_').lower()).getfloat(key) - return None + return default - def getboolean(self, section, key): + def getboolean(self, section, key, default=None): if hasattr(self, section.replace('-','_').lower()): return getattr(self, section.replace('-', '_').lower()).getboolean(key) - return False + return default def parse_capes(self): """ Read the name and revision of each cape on the BeagleBone """ @@ -96,10 +106,36 @@ def parse_capes(self): self.reach_revision = data[38:42] self.reach_data = data self.reach_path = path - if self.replicape_revision != None and self.reach_revision != None: + if self.replicape_revision is not None and self.reach_revision is not None: break except IOError as e: pass def save(self, filename): raise NotImplemented("not yet implemented") + + def _gen_key(self): + """ Used to generate a key when one is not found """ + return ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(20)) + + def get_key(self): + """ Get the generated key from the config or create one """ + + self.replicape_key = "".join(struct.unpack('20c', self.replicape_data[100:120])) + + logging.debug("Found Replicape key: '"+self.replicape_key+"'") + + if self.replicape_key == '\x00'*20: + logging.debug("Replicape key invalid") + + self.replicape_key = self._gen_key() + self.replicape_data = self.replicape_data[:100] + self.replicape_key + + logging.debug("New Replicape key: '"+self.replicape_key+"'") + + try: + with open(self.replicape_path, "wb") as f: + f.write(self.replicape_data[:120]) + except IOError as e: + logging.warning("Unable to write new key to EEPROM") + return self.replicape_key diff --git a/redeem/configuration/__init__.py b/redeem/configuration/__init__.py index d11f8ed7..e6e0ff38 100644 --- a/redeem/configuration/__init__.py +++ b/redeem/configuration/__init__.py @@ -1,4 +1,4 @@ -from ConfigParser import SafeConfigParser +from ConfigParser import RawConfigParser from redeem.configuration.factories.ConfigFactoryV19 import ConfigFactoryV19 from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 @@ -6,7 +6,7 @@ def get_config_factory(config_files): - config_parser = SafeConfigParser() + config_parser = RawConfigParser() config_parser.read(config_files) version = None From 171dc2023aff769a03bbab06201c16b488a97b68 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Mon, 6 Nov 2017 15:45:48 -0500 Subject: [PATCH 08/14] fixing getboolean to handle string or bool. setting default attribute on Redeem --- redeem/Printer.py | 3 ++- redeem/configuration/RedeemConfig.py | 24 ++++++++++++++-------- redeem/configuration/exceptions.py | 6 ++++++ redeem/configuration/factories/__init__.py | 1 - redeem/configuration/sections/__init__.py | 24 +++++++++++++--------- redeem/configuration/utils.py | 3 +++ tests/gcode/MockPrinter.py | 18 ++++++++++++---- tests/gcode/test_M114.py | 3 +-- 8 files changed, 56 insertions(+), 26 deletions(-) create mode 100644 redeem/configuration/exceptions.py create mode 100644 redeem/configuration/utils.py diff --git a/redeem/Printer.py b/redeem/Printer.py index 689ca4d9..4c743ecf 100755 --- a/redeem/Printer.py +++ b/redeem/Printer.py @@ -109,8 +109,9 @@ def __init__(self): self.sd_card_manager = SDCardManager() # default. should be initialized later - # TODO : should be passed into from constructor + # TODO : these should be passed into from constructor self.config = RedeemConfig() + self.replicape_key = None def add_slave(self, master, slave): ''' Make an axis copy the movement of another. diff --git a/redeem/configuration/RedeemConfig.py b/redeem/configuration/RedeemConfig.py index 2271816b..9451e5b9 100644 --- a/redeem/configuration/RedeemConfig.py +++ b/redeem/configuration/RedeemConfig.py @@ -3,6 +3,9 @@ import random import string +from redeem.configuration.exceptions import InvalidConfigSectionException +from redeem.configuration.utils import clean_key as _ + from redeem.configuration.sections.alarms import AlarmsConfig from redeem.configuration.sections.coldends import ColdendsConfig from redeem.configuration.sections.delta import DeltaConfig @@ -58,32 +61,37 @@ class RedeemConfig(object): reach_path = None def get(self, section, key, default=None): - if hasattr(self, section.replace('-', '_').lower()): - return getattr(self, section.replace('-', '_').lower()).get(key) + if hasattr(self, _(section)): + return getattr(self, _(section)).get(key) return default def has(self, section, key): - return hasattr(self, section.replace('-', '_').lower()) and getattr(self, section.replace('-', '_').lower()).has(key) + return hasattr(self, _(section)) and getattr(self, _(section)).has(key) # alias def has_option(self, section, key): return self.has(section, key) def getint(self, section, key, default=None): - if hasattr(self, section.replace('-', '_').lower()): - return getattr(self, section.replace('-', '_').lower()).getint(key) + if hasattr(self, _(section)): + return getattr(self, _(section)).getint(key) return default def getfloat(self, section, key, default=None): - if hasattr(self, section.replace('-', '_').lower()): - return getattr(self, section.replace('-', '_').lower()).getfloat(key) + if hasattr(self, _(section)): + return getattr(self, _(section)).getfloat(key) return default def getboolean(self, section, key, default=None): if hasattr(self, section.replace('-','_').lower()): - return getattr(self, section.replace('-', '_').lower()).getboolean(key) + return getattr(self, _(section)).getboolean(key) return default + def set(self, section, key, val): + if not hasattr(self, _(section)): + raise InvalidConfigSectionException() + getattr(self, _(section)).set(key, val) + def parse_capes(self): """ Read the name and revision of each cape on the BeagleBone """ self.replicape_revision = None diff --git a/redeem/configuration/exceptions.py b/redeem/configuration/exceptions.py new file mode 100644 index 00000000..e5155b05 --- /dev/null +++ b/redeem/configuration/exceptions.py @@ -0,0 +1,6 @@ +class InvalidConfigSectionException(Exception): + pass + + +class InvalidConfigOptionException(Exception): + pass diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index c0004b4e..8c9a029c 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -1,6 +1,5 @@ from ConfigParser import SafeConfigParser -from redeem.CascadingConfigParser import CascadingConfigParser from redeem.configuration.RedeemConfig import RedeemConfig from redeem.configuration.sections.alarms import AlarmsConfig from redeem.configuration.sections.coldends import ColdendsConfig diff --git a/redeem/configuration/sections/__init__.py b/redeem/configuration/sections/__init__.py index bbdb4c26..e9ba7b6c 100644 --- a/redeem/configuration/sections/__init__.py +++ b/redeem/configuration/sections/__init__.py @@ -1,21 +1,25 @@ -def _clean(key): - return key.replace('-','_').lower() +from redeem.configuration.utils import clean_key as _ +from redeem.configuration.exceptions import InvalidConfigOptionException class BaseConfig(object): """Superclass of all config 'sections'""" def has(self, key): - return hasattr(self, _clean(key)) + return hasattr(self, _(key)) def get(self, key): - if not hasattr(self, _clean(key)): - print("*****{}".format(key)) + if not hasattr(self, _(key)): return None - return getattr(self, _clean(key)) + return getattr(self, _(key)) + + def set(self, key, val): + if not hasattr(self, _(key)): + raise InvalidConfigOptionException() + setattr(self, _(key), val) def getfloat(self, key): - val = self.get(_clean(key)) + val = self.get(_(key)) try: val = float(val) except ValueError: @@ -23,7 +27,7 @@ def getfloat(self, key): return val def getint(self, key): - val = self.get(_clean(key)) + val = self.get(_(key)) try: val = int(val) except ValueError: @@ -31,6 +35,6 @@ def getint(self, key): return val def getboolean(self, key): - val = self.get(_clean(key)) - return val in ['True', 'true'] + val = self.get(_(key)) + return val in ['True', 'true', True] diff --git a/redeem/configuration/utils.py b/redeem/configuration/utils.py new file mode 100644 index 00000000..3b2cd009 --- /dev/null +++ b/redeem/configuration/utils.py @@ -0,0 +1,3 @@ +def clean_key(key): + return key.replace('-', '_').lower() + diff --git a/tests/gcode/MockPrinter.py b/tests/gcode/MockPrinter.py index a3c0b2e4..542d12d9 100644 --- a/tests/gcode/MockPrinter.py +++ b/tests/gcode/MockPrinter.py @@ -14,6 +14,7 @@ sys.modules['GPIO'] = mock.Mock() sys.modules['DAC'] = mock.Mock() sys.modules['ShiftRegister.py'] = mock.Mock() +sys.modules['Adafruit_I2C'] = mock.Mock() sys.modules['Adafruit_BBIO'] = mock.Mock() sys.modules['Adafruit_BBIO.GPIO'] = mock.Mock() sys.modules['StepperWatchdog'] = mock.Mock() @@ -30,8 +31,13 @@ sys.modules['Pipe'] = mock.Mock() from CascadingConfigParser import CascadingConfigParser -from Redeem import * +from Redeem import Redeem +from PathPlanner import PathPlanner from EndStop import EndStop +from Extruder import Extruder, HBP +from Path import Path +from Gcode import Gcode +import numpy as np """ @@ -83,11 +89,15 @@ def setUpConfigFiles(cls, path): @classmethod @mock.patch.object(EndStop, "_wait_for_event", new=None) @mock.patch.object(PathPlanner, "_init_path_planner") - @mock.patch.object(CascadingConfigParser, "get_key") + # @mock.patch.object(RedeemConfig, "get_key") @mock.patch("Redeem.CascadingConfigParser", new=CascadingConfigParserWedge) - def setUpClass(cls, mock_get_key, mock_init_path_planner): + def setUpClass(cls, mock_init_path_planner): - mock_get_key.return_value = "TESTING_DUMMY_KEY" + config_patch = mock.patch("configuration.RedeemConfig.RedeemConfig", revision="TESTING_REVISION", get_key="TESTING_DUMMY_KEY") + config_mock = config_patch.start() + + pwm_patch = mock.patch("Redeem.PWM.i2c") + pwm_mock = pwm_patch.start() """ Allow Extruder or HBP instantiation without crashing 'cause not BBB/Replicape diff --git a/tests/gcode/test_M114.py b/tests/gcode/test_M114.py index 8a1be737..6a50144b 100644 --- a/tests/gcode/test_M114.py +++ b/tests/gcode/test_M114.py @@ -30,10 +30,9 @@ def test_gcodes_M114(self): ) g = Gcode({"message": "M114"}) self.printer.processor.gcodes[g.gcode].execute(g) - self.printer.path_planner.get_current_pos.assert_called_with(mm=True) # kinda redundant, but hey. + self.printer.path_planner.get_current_pos.assert_called_with(ideal=True, mm=True) # kinda redundant, but hey. self.assertEqual(g.answer, "ok C: X:{:.1f} Y:{:.1f} Z:{:.1f} E:{:.1f} A:{:.1f} B:{:.1f} C:{:.1f} H:{:.1f}".format( X, Y, Z, E, A, B, C, H ) ) - From d2e7cea63d8352d38bbf8e88226bc6f133139689 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Tue, 7 Nov 2017 16:32:29 -0500 Subject: [PATCH 09/14] updating test to use correct stepper type --- tests/gcode/MockPrinter.py | 1 + tests/gcode/test_M19.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/gcode/MockPrinter.py b/tests/gcode/MockPrinter.py index 542d12d9..271c2ff1 100644 --- a/tests/gcode/MockPrinter.py +++ b/tests/gcode/MockPrinter.py @@ -118,6 +118,7 @@ def enable(self): cls.R = Redeem(config_location=cfg_path) cls.printer = cls.R.printer + cls.printer.reach_revision = "00B0" cls.setUpPatch() diff --git a/tests/gcode/test_M19.py b/tests/gcode/test_M19.py index 7ff93b38..d7b09db8 100644 --- a/tests/gcode/test_M19.py +++ b/tests/gcode/test_M19.py @@ -1,11 +1,11 @@ from MockPrinter import MockPrinter import mock from random import random -from Stepper import Stepper_00A4 +from Stepper import Stepper_00B3 class M19_Tests(MockPrinter): - @mock.patch.object(Stepper_00A4, "reset") + @mock.patch.object(Stepper_00B3, "reset") def test_gcodes_M19(self, m): self.execute_gcode("M19") self.printer.path_planner.wait_until_done.assert_called() From f169191ce2cee9d9d7e0384a41edacba54e639b6 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Fri, 10 Nov 2017 13:16:51 -0500 Subject: [PATCH 10/14] removing cascading config parser as it's no longer needed --- redeem/CascadingConfigParser.py | 189 -------------------------------- redeem/Redeem.py | 1 - tests/gcode/MockPrinter.py | 11 -- 3 files changed, 201 deletions(-) delete mode 100644 redeem/CascadingConfigParser.py diff --git a/redeem/CascadingConfigParser.py b/redeem/CascadingConfigParser.py deleted file mode 100644 index 3b22edd6..00000000 --- a/redeem/CascadingConfigParser.py +++ /dev/null @@ -1,189 +0,0 @@ -""" -Author: Elias Bakken -email: elias(dot)bakken(at)gmail(dot)com -Website: http://www.thing-printer.com -License: GNU GPL v3: http://www.gnu.org/copyleft/gpl.html - - Redeem is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Redeem is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Redeem. If not, see . -""" - -import ConfigParser -import os -import logging -import struct - - -class CascadingConfigParser(ConfigParser.SafeConfigParser): - def __init__(self, config_files): - - ConfigParser.SafeConfigParser.__init__(self) - - # Write options in the case it was read. - # self.optionxform = str - - # Parse to real path - self.config_files = [] - for config_file in config_files: - self.config_files.append(os.path.realpath(config_file)) - self.config_location = os.path.dirname(os.path.realpath(config_file)) - - # Parse all config files in list - for config_file in self.config_files: - if os.path.isfile(config_file): - logging.info("Using config file " + config_file) - self.readfp(open(config_file)) - else: - logging.warning("Missing config file " + config_file) - # Might also add command line options for overriding stuff - - def timestamp(self): - """ Get the largest (newest) timestamp for all the config files. """ - ts = 0 - for config_file in self.config_files: - if os.path.isfile(config_file): - ts = max(ts, os.path.getmtime(config_file)) - - printer_cfg = os.path.join(self.config_location, "printer.cfg") - if os.path.islink(printer_cfg): - ts = max(ts, os.lstat(printer_cfg).st_mtime) - return ts - - def parse_capes(self): - """ Read the name and revision of each cape on the BeagleBone """ - self.replicape_revision = None - self.reach_revision = None - - import glob - paths = glob.glob("/sys/bus/i2c/devices/[1-2]-005[4-7]/*/nvmem") - paths.extend(glob.glob("/sys/bus/i2c/devices/[1-2]-005[4-7]/nvmem/at24-[1-4]/nvmem")) - #paths.append(glob.glob("/sys/bus/i2c/devices/[1-2]-005[4-7]/eeprom")) - for i, path in enumerate(paths): - try: - with open(path, "rb") as f: - data = f.read(120) - name = data[58:74].strip() - if name == "BB-BONE-REPLICAP": - self.replicape_revision = data[38:42] - self.replicape_data = data - self.replicape_path = path - elif name[:13] == "BB-BONE-REACH": - self.reach_revision = data[38:42] - self.reach_data = data - self.reach_path = path - if self.replicape_revision != None and self.reach_revision != None: - break - except IOError as e: - pass - return - - def get_default_settings(self): - fs = [] - for config_file in self.config_files: - if os.path.isfile(config_file): - c_file = os.path.basename(config_file) - cp = ConfigParser.SafeConfigParser() - cp.readfp(open(config_file)) - fs.append((c_file, cp)) - - lines = [] - for section in self.sections(): - for option in self.options(section): - for (name, cp) in fs: - if cp.has_option(section, option): - line = [name, section, option, cp.get(section, option)] - lines.append(line) - - return lines - - - def save(self, filename): - """ Save the changed settings to local.cfg """ - current = CascadingConfigParser(self.config_files) - - # Get list of changed values - to_save = [] - for section in self.sections(): - #logging.debug(section) - for option in self.options(section): - if self.get(section, option) != current.get(section, option): - old = current.get(section, option) - val = self.get(section, option) - to_save.append((section, option, val, old)) - - # Update local config with changed values - local = ConfigParser.SafeConfigParser() - local.readfp(open(filename, "r")) - for opt in to_save: - (section, option, value, old) = opt - if not local.has_section(section): - local.add_section(section) - local.set(section, option, value) - logging.info("Update setting: {} from {} to {} ".format(option, old, value)) - - - # Save changed values to file - local.write(open(filename, "w+")) - - - def check(self, filename): - """ Check the settings currently set against default.cfg """ - default = ConfigParser.SafeConfigParser() - default.readfp(open(os.path.join(self.config_location, "default.cfg"))) - local = ConfigParser.SafeConfigParser() - local.readfp(open(filename)) - - local_ok = True - diff = set(local.sections())-set(default.sections()) - for section in diff: - logging.warning("Section {} does not exist in {}".format(section, "default.cfg")) - local_ok = False - for section in local.sections(): - if not default.has_section(section): - continue - diff = set(local.options(section))-set(default.options(section)) - for option in diff: - logging.warning("Option {} in section {} does not exist in {}".format(option, section, "default.cfg")) - local_ok = False - if local_ok: - logging.info("{} is OK".format(filename)) - else: - logging.warning("{} contains errors.".format(filename)) - return local_ok - - def get_key(self): - """ Get the generated key from the config or create one """ - self.replicape_key = "".join(struct.unpack('20c', self.replicape_data[100:120])) - logging.debug("Found Replicape key: '"+self.replicape_key+"'") - if self.replicape_key == '\x00'*20: - logging.debug("Replicape key invalid") - import random - import string - self.replicape_key = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(20)) - self.replicape_data = self.replicape_data[:100] + self.replicape_key - logging.debug("New Replicape key: '"+self.replicape_key+"'") - #logging.debug("".join(struct.unpack('20c', self.new_replicape_data[100:120]))) - try: - with open(self.replicape_path, "wb") as f: - f.write(self.replicape_data[:120]) - except IOError as e: - logging.warning("Unable to write new key to EEPROM") - return self.replicape_key - - -if __name__ == '__main__': - logging.basicConfig(level=logging.DEBUG, - format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s', - datefmt='%m-%d %H:%M') - c = CascadingConfigParser(["/etc/redeem/default.cfg", "/etc/redeem/printer.cfg", "/etc/redeem/local.cfg"]) - print c.get_default_settings() diff --git a/redeem/Redeem.py b/redeem/Redeem.py index dbd8c31a..6c296465 100755 --- a/redeem/Redeem.py +++ b/redeem/Redeem.py @@ -52,7 +52,6 @@ from Gcode import Gcode from ColdEnd import ColdEnd from PruFirmware import PruFirmware -from CascadingConfigParser import CascadingConfigParser from Printer import Printer from GCodeProcessor import GCodeProcessor from PluginsController import PluginsController diff --git a/tests/gcode/MockPrinter.py b/tests/gcode/MockPrinter.py index 271c2ff1..dd3caa91 100644 --- a/tests/gcode/MockPrinter.py +++ b/tests/gcode/MockPrinter.py @@ -30,7 +30,6 @@ sys.modules['Ethernet'] = mock.Mock() sys.modules['Pipe'] = mock.Mock() -from CascadingConfigParser import CascadingConfigParser from Redeem import Redeem from PathPlanner import PathPlanner from EndStop import EndStop @@ -40,15 +39,6 @@ import numpy as np -""" -Override CascadingConfigParser methods to set self. variables -""" -class CascadingConfigParserWedge(CascadingConfigParser): - def parse_capes(self): - self.replicape_revision = "0A4A" # Fake. No hardware involved in these tests (Redundant?) - self.reach_revision = "00A0" # Fake. No hardware involved in these tests (Redundant?) - - class MockPrinter(unittest.TestCase): """ @@ -90,7 +80,6 @@ def setUpConfigFiles(cls, path): @mock.patch.object(EndStop, "_wait_for_event", new=None) @mock.patch.object(PathPlanner, "_init_path_planner") # @mock.patch.object(RedeemConfig, "get_key") - @mock.patch("Redeem.CascadingConfigParser", new=CascadingConfigParserWedge) def setUpClass(cls, mock_init_path_planner): config_patch = mock.patch("configuration.RedeemConfig.RedeemConfig", revision="TESTING_REVISION", get_key="TESTING_DUMMY_KEY") From 8b0930e61653de3af416cb082f8da1a45336aafe Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Fri, 10 Nov 2017 13:49:08 -0500 Subject: [PATCH 11/14] applying python 3 package changes --- redeem/configuration/__init__.py | 7 +++++-- redeem/configuration/factories/__init__.py | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/redeem/configuration/__init__.py b/redeem/configuration/__init__.py index e6e0ff38..9a7fcb2e 100644 --- a/redeem/configuration/__init__.py +++ b/redeem/configuration/__init__.py @@ -1,4 +1,7 @@ -from ConfigParser import RawConfigParser +try: # TODO: remove when migrate to python 3 + from confingparser import ConfigParser +except ImportError: + from ConfigParser import RawConfigParser as ConfigParser from redeem.configuration.factories.ConfigFactoryV19 import ConfigFactoryV19 from redeem.configuration.factories.ConfigFactoryV20 import ConfigFactoryV20 @@ -6,7 +9,7 @@ def get_config_factory(config_files): - config_parser = RawConfigParser() + config_parser = ConfigParser() config_parser.read(config_files) version = None diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index 8c9a029c..d0a0d1c5 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -1,4 +1,8 @@ -from ConfigParser import SafeConfigParser +try: # TODO: remove when migrate to python 3 + from confingparser import ConfigParser +except ImportError: + from ConfigParser import SafeConfigParser as ConfigParser + from redeem.configuration.RedeemConfig import RedeemConfig from redeem.configuration.sections.alarms import AlarmsConfig @@ -56,7 +60,7 @@ def __init__(self): def hydrate_config(self, config_file=None, config_files=()): """Use default mapper, unless another one is specified by subclass""" - config_parser = SafeConfigParser() + config_parser = ConfigParser() if config_file is not None and len(config_files) > 0: raise Exception("cannot provide both single and list of config files") From d1f4ec4ce025aec3988775c3e04b776ffc6de4a4 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Fri, 10 Nov 2017 13:51:31 -0500 Subject: [PATCH 12/14] fix typo --- redeem/configuration/__init__.py | 2 +- redeem/configuration/factories/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/redeem/configuration/__init__.py b/redeem/configuration/__init__.py index 9a7fcb2e..cefebea3 100644 --- a/redeem/configuration/__init__.py +++ b/redeem/configuration/__init__.py @@ -1,5 +1,5 @@ try: # TODO: remove when migrate to python 3 - from confingparser import ConfigParser + from configparser import ConfigParser except ImportError: from ConfigParser import RawConfigParser as ConfigParser diff --git a/redeem/configuration/factories/__init__.py b/redeem/configuration/factories/__init__.py index d0a0d1c5..a2b8dda4 100644 --- a/redeem/configuration/factories/__init__.py +++ b/redeem/configuration/factories/__init__.py @@ -1,5 +1,5 @@ try: # TODO: remove when migrate to python 3 - from confingparser import ConfigParser + from configparser import ConfigParser except ImportError: from ConfigParser import SafeConfigParser as ConfigParser From fec07ad4e1a33a50842230761fbb221ed59ba939 Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Fri, 10 Nov 2017 16:57:33 -0500 Subject: [PATCH 13/14] fixing replicape key setup --- tests/gcode/MockPrinter.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/gcode/MockPrinter.py b/tests/gcode/MockPrinter.py index 542d12d9..89f95394 100644 --- a/tests/gcode/MockPrinter.py +++ b/tests/gcode/MockPrinter.py @@ -93,9 +93,6 @@ def setUpConfigFiles(cls, path): @mock.patch("Redeem.CascadingConfigParser", new=CascadingConfigParserWedge) def setUpClass(cls, mock_init_path_planner): - config_patch = mock.patch("configuration.RedeemConfig.RedeemConfig", revision="TESTING_REVISION", get_key="TESTING_DUMMY_KEY") - config_mock = config_patch.start() - pwm_patch = mock.patch("Redeem.PWM.i2c") pwm_mock = pwm_patch.start() @@ -118,6 +115,7 @@ def enable(self): cls.R = Redeem(config_location=cfg_path) cls.printer = cls.R.printer + cls.printer.replicape_key = "TESTING_DUMMY_KEY" cls.setUpPatch() From 06407a5047189f16eefedcf9104118b77fe363cf Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Wed, 6 Dec 2017 18:44:23 -0500 Subject: [PATCH 14/14] update description for config test. although it is a no-op now, V19 config factory will now inherit future changes. previous merge broke test suite, now fixed --- redeem/configuration/factories/ConfigFactoryV19.py | 4 ++-- tests/core/test_configs.py | 11 ++--------- tests/gcode/MockPrinter.py | 10 +--------- tests/gcode/test_M115.py | 2 +- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/redeem/configuration/factories/ConfigFactoryV19.py b/redeem/configuration/factories/ConfigFactoryV19.py index 422b6578..a76399e1 100644 --- a/redeem/configuration/factories/ConfigFactoryV19.py +++ b/redeem/configuration/factories/ConfigFactoryV19.py @@ -1,6 +1,6 @@ import numpy as np -from redeem.configuration.factories import ConfigFactory +from configuration import ConfigFactoryV20 from redeem.configuration.sections.delta import DeltaConfig @@ -14,7 +14,7 @@ def _radiansToDegrees(radians): return radians * 180 / np.pi -class ConfigFactoryV19(ConfigFactory): +class ConfigFactoryV19(ConfigFactoryV20): def _calc_old_column_position(self, r, ae, be, ce, diff --git a/tests/core/test_configs.py b/tests/core/test_configs.py index 016d3f57..5bd3df41 100644 --- a/tests/core/test_configs.py +++ b/tests/core/test_configs.py @@ -88,7 +88,7 @@ def test_delta(self): self.assertAlmostEqual(redeem_config.getfloat('delta', 'c_angular'), 0.0) def test_old_config_into_new(self): - """test to make sure delta corrections are zero when tangential and angular are zero""" + """test to make sure delta corrections are converted correctly from 1.9 format to 2.0 format""" factory = ConfigFactoryV19() @@ -98,14 +98,7 @@ def test_old_config_into_new(self): ] redeem_config = factory.hydrate_config(config_files=files) - - # print("a radial : {}".format(redeem_config.getfloat('delta', 'a_radial'))) - # print("a angular : {}".format(redeem_config.getfloat('delta', 'a_angular'))) - # print("b radial : {}".format(redeem_config.getfloat('delta', 'b_radial'))) - # print("b angular : {}".format(redeem_config.getfloat('delta', 'b_angular'))) - # print("c radial : {}".format(redeem_config.getfloat('delta', 'c_radial'))) - # print("c angular : {}".format(redeem_config.getfloat('delta', 'c_angular'))) - + self.assertAlmostEqual(redeem_config.getfloat('delta', 'a_radial'), 0.0010672079121700762) self.assertAlmostEqual(redeem_config.getfloat('delta', 'a_angular'), -1.9251837083231607) diff --git a/tests/gcode/MockPrinter.py b/tests/gcode/MockPrinter.py index 450cad37..7a85d2b4 100644 --- a/tests/gcode/MockPrinter.py +++ b/tests/gcode/MockPrinter.py @@ -33,10 +33,6 @@ sys.modules['redeem.USB'] = mock.Mock() sys.modules['redeem.Ethernet'] = mock.Mock() sys.modules['redeem.Pipe'] = mock.Mock() -sys.modules['redeem.Fan'] = mock.Mock() -sys.modules['redeem.Mosfet'] = mock.Mock() -sys.modules['redeem.PWM'] = mock.Mock() - from redeem.Redeem import Redeem from redeem.PathPlanner import PathPlanner @@ -46,7 +42,6 @@ from redeem.Gcode import Gcode - class MockPrinter(unittest.TestCase): """ @@ -87,12 +82,8 @@ def setUpConfigFiles(cls, path): @classmethod @mock.patch.object(EndStop, "_wait_for_event", new=None) @mock.patch.object(PathPlanner, "_init_path_planner") - # @mock.patch.object(RedeemConfig, "get_key") def setUpClass(cls, mock_init_path_planner): - # pwm_patch = mock.patch("redeem.PWM.PWM.i2c") - # pwm_mock = pwm_patch.start() - """ Allow Extruder or HBP instantiation without crashing 'cause not BBB/Replicape """ @@ -110,6 +101,7 @@ def bypass_init_path_planner(self): mock.patch('redeem.Extruder.Extruder.enable', new=disabled_extruder_enable).start() mock.patch('redeem.Extruder.HBP.enable', new=disabled_hbp_enable).start() mock.patch('redeem.PathPlanner.PathPlanner._init_path_planner', new=bypass_init_path_planner) + mock.patch("redeem.PWM.PWM.i2c").start() cfg_path = "../configs" cls.setUpConfigFiles(cfg_path) diff --git a/tests/gcode/test_M115.py b/tests/gcode/test_M115.py index 6d604b56..1f1dc00f 100644 --- a/tests/gcode/test_M115.py +++ b/tests/gcode/test_M115.py @@ -16,7 +16,7 @@ def test_gcodes_M115(self): self.assertRegexpMatches(g.answer, "REPLICAPE_KEY:TESTING_DUMMY_KEY") self.assertRegexpMatches(g.answer, "FIRMWARE_NAME:Redeem") self.assertRegexpMatches(g.answer, "FIRMWARE_VERSION:{}\s".format(re.escape(self.printer.firmware_version))) - self.assertRegexpMatches(g.answer, "FIRMWARE_URL:https?%3A\S+") + self.assertRegexpMatches(g.answer, r"FIRMWARE_URL:https://") self.assertRegexpMatches(g.answer, "MACHINE_TYPE:{}\s".format( re.escape(self.printer.config.get('System', 'machine_type')))) self.assertRegexpMatches(g.answer, "EXTRUDER_COUNT:{}".format(self.printer.NUM_AXES - 3))