From cb09ec8b9e509905c20b0f3d9ed6b073267d2c12 Mon Sep 17 00:00:00 2001 From: Desuuuu Date: Sun, 16 Aug 2020 14:51:53 +0200 Subject: [PATCH] Integrate support for DGUS T5UID1 touchscreens --- README.md | 12 + config/sample-t5uid1.cfg | 26 + klippy/extras/t5uid1/__init__.py | 9 + .../extras/t5uid1/dgus_reloaded/__init__.py | 110 +++ klippy/extras/t5uid1/dgus_reloaded/pages.cfg | 112 +++ .../extras/t5uid1/dgus_reloaded/routines.cfg | 207 ++++ .../extras/t5uid1/dgus_reloaded/vars_in.cfg | 706 ++++++++++++++ .../extras/t5uid1/dgus_reloaded/vars_out.cfg | 461 +++++++++ klippy/extras/t5uid1/page.py | 38 + klippy/extras/t5uid1/routine.py | 102 ++ klippy/extras/t5uid1/t5uid1.py | 891 ++++++++++++++++++ klippy/extras/t5uid1/var.py | 154 +++ src/avr/Kconfig | 28 + src/avr/Makefile | 2 + src/avr/serial_t5uid1.c | 93 ++ src/generic/t5uid1_irq.c | 245 +++++ src/generic/t5uid1_irq.h | 14 + src/lpc176x/Kconfig | 5 + src/lpc176x/Makefile | 2 + src/lpc176x/serial_t5uid1.c | 80 ++ 20 files changed, 3297 insertions(+) create mode 100644 config/sample-t5uid1.cfg create mode 100644 klippy/extras/t5uid1/__init__.py create mode 100644 klippy/extras/t5uid1/dgus_reloaded/__init__.py create mode 100644 klippy/extras/t5uid1/dgus_reloaded/pages.cfg create mode 100644 klippy/extras/t5uid1/dgus_reloaded/routines.cfg create mode 100644 klippy/extras/t5uid1/dgus_reloaded/vars_in.cfg create mode 100644 klippy/extras/t5uid1/dgus_reloaded/vars_out.cfg create mode 100644 klippy/extras/t5uid1/page.py create mode 100644 klippy/extras/t5uid1/routine.py create mode 100644 klippy/extras/t5uid1/t5uid1.py create mode 100644 klippy/extras/t5uid1/var.py create mode 100644 src/avr/serial_t5uid1.c create mode 100644 src/generic/t5uid1_irq.c create mode 100644 src/generic/t5uid1_irq.h create mode 100644 src/lpc176x/serial_t5uid1.c diff --git a/README.md b/README.md index b4c7a7b4..1e4aa379 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,15 @@ To begin using Klipper start by Klipper is Free Software. See the [license](COPYING) or read the [documentation](https://www.klipper3d.org/Overview.html). + +## Modifications + +The scope of modifications is limited to adding support for DGUS +touchscreens. This feature is only available for AVR/LPC176X +micro-controllers and it needs to be configured before compilation. + +The touchscreen firmware compatible with this fork is available in +[this repository](https://github.com/Desuuuu/DGUS-reloaded-Klipper). + +Available configuration options are documented in the +[sample-t5uid1.cfg](/config/sample-t5uid1.cfg) file. diff --git a/config/sample-t5uid1.cfg b/config/sample-t5uid1.cfg new file mode 100644 index 00000000..8ecb5944 --- /dev/null +++ b/config/sample-t5uid1.cfg @@ -0,0 +1,26 @@ +# This file provides example configuration for DGUS T5UID1 touchscreens. + +[t5uid1] +firmware: dgus_reloaded +# This controls how Klipper interacts with the touchscreen. The only possible +# value is "dgus_reloaded" at the moment. This parameter must be provided. +#update_interval: 2 +# How often to send data updates to the touchscreen. +#volume: 75 +# The volume for the touchscreen speaker (as a value from 0 to 100). +#brightness: 100 +# The brightness for the touchscreen (as a value from 0 to 100). +#machine_name: Generic 3D Printer +# The machine name shown on the information page. +#boot_sound: +# The index of the sound to play when booting. Defaults to being provided by +# the selected firmware. +#notification_sound: +# The index of the sound to play when certain events happened. Defaults to +# being provided by the selected firmware. +#z_min: +# This can be used to provide a lower limit to Z moves. Movements will still +# be limited by your [stepper_z] configuration values. +#z_max: +# This can be used to provide an upper limit to Z moves. Movements will still +# be limited by your [stepper_z] configuration values. diff --git a/klippy/extras/t5uid1/__init__.py b/klippy/extras/t5uid1/__init__.py new file mode 100644 index 00000000..320ec40f --- /dev/null +++ b/klippy/extras/t5uid1/__init__.py @@ -0,0 +1,9 @@ +# Package definition for the extras/t5uid1 directory +# +# Copyright (C) 2020 Desuuuu +# +# This file may be distributed under the terms of the GNU GPLv3 license. +from . import t5uid1 + +def load_config(config): + return t5uid1.load_config(config) diff --git a/klippy/extras/t5uid1/dgus_reloaded/__init__.py b/klippy/extras/t5uid1/dgus_reloaded/__init__.py new file mode 100644 index 00000000..47e46c42 --- /dev/null +++ b/klippy/extras/t5uid1/dgus_reloaded/__init__.py @@ -0,0 +1,110 @@ +# Package definition for the extras/t5uid1/dugs_reloaded directory +# +# Copyright (C) 2020 Desuuuu +# +# This file may be distributed under the terms of the GNU GPLv3 license. +controls = { + # print_status + 'pause': 1, # popup_window + 'resume': 2, # popup_window + + # leveling_automatic + 'disable': 5, # return_key_code + + # settings_menu2 + 'extra2': 4, # return_key_code + + # wait + 'abort': 1, # popup_window + 'continue': 2 # return_key_code +} + +constants = { + 'temp_pla': { + 'hotend': 200, + 'bed': 60 + }, + 'temp_abs': { + 'hotend': 240, + 'bed': 80 + }, + 'temp_petg': { + 'hotend': 240, + 'bed': 60 + }, + + 'popup_confirmed': 1, + + 'adjust_increment': 0, + 'adjust_decrement': 1, + + 'preset_pla': 1, + 'preset_abs': 2, + 'preset_petg': 3, + + 'extruder_current': -1, + 'extruder_e0': 0, + 'extruder_e1': 1, + + 'heater_all': -2, + 'heater_bed': -1, + 'heater_h0': 0, + 'heater_h1': 1, + + 'stepper_enable': 1, + 'stepper_disable': 2, + + 'step_size_10': 0, + 'step_size_1': 1, + 'step_size_0.1': 2, + 'step_size_0.01': 3, + + 'filament_retract': 0, + 'filament_extrude': 1, + + 'axis_xyz': 0, + 'axis_xy': 1, + 'axis_z': 2, + + 'move_x+': 0, + 'move_x-': 1, + 'move_y+': 2, + 'move_y-': 3, + 'move_z+': 4, + 'move_z-': 5, + + 'extra_button1': 0, + 'extra_button2': 1, + + 'disabled': 0, + 'enabled': 1, + + 'status_icon_pause': 1 << 0, + 'status_icon_resume': 1 << 1, + + 'step_icon_10': 1 << 0, + 'step_icon_1': 1 << 1, + 'step_icon_0.1': 1 << 2, + 'step_icon_0.01': 1 << 3, + + 'extruder_icon_e0': 1 << 0, + 'extruder_icon_e1': 1 << 1, + + 'heater_icon_bed': 1 << 0, + 'heater_icon_h0': 1 << 1, + 'heater_icon_h1': 1 << 2, + + 'wait_icon_abort': 1 << 0, + 'wait_icon_continue': 1 << 1 +} + +configuration = { + 'config_files': ['routines.cfg', + 'pages.cfg', + 'vars_in.cfg', + 'vars_out.cfg'], + 'boot_sound': 1, + 'notification_sound': 3, + 'controls': controls, + 'constants': constants +} diff --git a/klippy/extras/t5uid1/dgus_reloaded/pages.cfg b/klippy/extras/t5uid1/dgus_reloaded/pages.cfg new file mode 100644 index 00000000..1b21c7f3 --- /dev/null +++ b/klippy/extras/t5uid1/dgus_reloaded/pages.cfg @@ -0,0 +1,112 @@ +[t5uid1_page boot] +id: 0 +boot: true +timeout: true + +[t5uid1_page home] +id: 1 +var_auto: temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target + +[t5uid1_page print_status] +id: 2 +var_auto: + temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target, + status_position_z, status_ellapsed, status_percent, status_icons + +[t5uid1_page print_adjust] +id: 3 +var_auto: + temp_h0_target, temp_bed_target, fan_speed, + adjust_feedrate, adjust_flowrate, level_offset + +[t5uid1_page print_finished] +id: 4 +var: status_ellapsed, status_percent +var_auto: + temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target, + status_position_z + +[t5uid1_page temp_menu] +id: 5 +var_auto: temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target + +[t5uid1_page temp_manual] +id: 6 +var: temp_h0_max, temp_bed_max +var_auto: temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target + +[t5uid1_page fan] +id: 7 +var_auto: fan_speed + +[t5uid1_page move] +id: 8 +var: move_step_icons +var_auto: move_current_x, move_current_y, move_current_z + +[t5uid1_page settings_menu] +id: 9 +var_auto: stepper_status + +[t5uid1_page leveling_menu] +id: 10 + +[t5uid1_page leveling_offset] +id: 11 +var: level_offset_step_icons +var_auto: level_offset + +[t5uid1_page leveling_manual] +id: 12 +var_auto: temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target + +[t5uid1_page leveling_automatic] +id: 13 +var: level_auto_disable_icon, level_auto_grid +var_auto: + temp_h0_current, temp_h0_target, temp_bed_current, temp_bed_target + +[t5uid1_page leveling_probing] +id: 14 +var_auto: level_probing_icons + +[t5uid1_page filament] +id: 15 +var: filament_icons, filament_length +var_auto: temp_h0_current, temp_h0_target + +[t5uid1_page volume] +id: 16 +var: volume + +[t5uid1_page brightness] +id: 17 +var: brightness + +[t5uid1_page settings_menu2] +id: 18 +var: bltouch + +[t5uid1_page pid] +id: 19 +var: pid_heater_icons, pid_temp +var_auto: pid_kp, pid_ki, pid_kd + +[t5uid1_page info] +id: 20 +var: info_machine, info_build_volume, info_version + +[t5uid1_page debug1] +id: 240 + +[t5uid1_page debug2] +id: 241 + +[t5uid1_page wait] +id: 249 +var: line1, line2, line3, line4, wait_icons + +[t5uid1_page kill] +id: 250 +shutdown: true +var: line1, line2, line3, line4 diff --git a/klippy/extras/t5uid1/dgus_reloaded/routines.cfg b/klippy/extras/t5uid1/dgus_reloaded/routines.cfg new file mode 100644 index 00000000..d619dd1b --- /dev/null +++ b/klippy/extras/t5uid1/dgus_reloaded/routines.cfg @@ -0,0 +1,207 @@ +[t5uid1_routine message_timeout] +trigger: manual +delay: 30 +script: {% do set_message("") %} + +[t5uid1_routine print_start] +trigger: manual +delay: 0 +script: {% do switch_page("print_status") %} + +[t5uid1_routine print_end] +trigger: manual +delay: 0 +script: + {% set sound = printer.t5uid1.notification_sound %} + {% if sound >= 0 %} + {% do play_sound(sound) %} + {% endif %} + {% do switch_page("print_finished") %} + +[t5uid1_routine trigger_full_update] +trigger: manual +delay: 0 +script: {% do full_update() %} + +[t5uid1_routine show_wait_screen] +trigger: manual +delay: 0 +script: {% do switch_page("wait") %} + +[t5uid1_routine show_probing_screen] +trigger: manual +delay: 0 +script: {% do switch_page("leveling_probing") %} + +[t5uid1_routine reset_bltouch] +trigger: manual +script: + {% if printer.t5uid1.has_bltouch %} + BLTOUCH_DEBUG COMMAND=reset + BLTOUCH_DEBUG COMMAND=pin_up + {% endif %} +run_as_gcode: true + +[t5uid1_routine __wait_return] +trigger: enter +page: wait +interval: 1 +script: + {% if not is_busy() %} + {% set sound = get_variable("wait_return_sound", -1) %} + {% if sound >= 0 and sound <= 255 %} + {% do set_variable("wait_return_sound", -1) %} + {% do play_sound(sound) %} + {% endif %} + {% set page = get_variable("wait_return", "")|default("home", true) %} + {% do set_variable("wait_return", "") %} + {% do switch_page(page) %} + {% endif %} + +[t5uid1_routine __probing_return] +trigger: enter +page: leveling_probing +interval: 1 +script: + {% if not is_busy() %} + {% set sound = printer.t5uid1.notification_sound %} + {% if sound >= 0 %} + {% do play_sound(sound) %} + {% endif %} + {% do switch_page("leveling_automatic") %} + {% endif %} + +[t5uid1_routine __boot] +trigger: enter +page: boot +delay: 3 +script: {% do switch_page("home") %} + +[t5uid1_routine __print_status_pause] +trigger: enter +page: print_status +interval: 5 +script: {% do check_paused() %} + +[t5uid1_routine __print_adjust_pause] +trigger: enter +page: print_adjust +interval: 5 +script: {% do check_paused() %} + +[t5uid1_routine __move] +trigger: enter_pre +page: move +script: + {% if is_busy() %} + {% do set_message("Busy") %} + { abort_page_switch() } + {% else %} + {% do set_variable("move_steps", printer.t5uid1.constants.step_size_10) %} + {% endif %} + +[t5uid1_routine __leveling_menu] +trigger: enter_pre +page: leveling_menu +script: + {% if is_busy() %} + {% do set_message("Busy") %} + { abort_page_switch() } + {% elif printer.toolhead.homed_axes == "xyz" %} + {% set t5uid1 = printer.t5uid1 %} + {% set x = (t5uid1.limits.x_min + t5uid1.limits.x_max) / 2 %} + {% set y = (t5uid1.limits.y_min + t5uid1.limits.y_max) / 2 %} + {% if printer.gcode.move_zpos < 10 %} + G1 Z10 F600 + {% endif %} + G1 X{x} Y{y} F4800 + {% else %} + { abort_page_switch() } + {% do set_variable("line1", "") %} + {% do set_variable("line2", "Homing...") %} + {% do set_variable("line3", "") %} + {% do set_variable("line4", "") %} + {% do set_variable("wait_return", "leveling_menu") %} + G28 + {% do start_routine("show_wait_screen") %} + {% endif %} +run_as_gcode: true + +[t5uid1_routine __leveling_offset] +trigger: enter_pre +page: leveling_offset +script: + {% if is_busy() %} + {% do set_message("Busy") %} + { abort_page_switch() } + {% elif printer.toolhead.homed_axes == "xyz" %} + {% set t5uid1 = printer.t5uid1 %} + {% set x = (t5uid1.limits.x_min + t5uid1.limits.x_max) / 2 %} + {% set y = (t5uid1.limits.y_min + t5uid1.limits.y_max) / 2 %} + {% if printer.gcode.move_zpos < 5 %} + G1 Z5 F600 + {% endif %} + G1 X{x} Y{y} F4800 + G1 Z0 F300 + {% else %} + { abort_page_switch() } + {% do set_variable("line1", "") %} + {% do set_variable("line2", "Homing...") %} + {% do set_variable("line3", "") %} + {% do set_variable("line4", "") %} + {% do set_variable("wait_return", "leveling_offset") %} + G28 + {% do start_routine("show_wait_screen") %} + {% endif %} +run_as_gcode: true + +[t5uid1_routine __leveling_manual] +trigger: enter_pre +page: leveling_manual +script: + {% if is_busy() %} + {% do set_message("Busy") %} + { abort_page_switch() } + {% elif printer.toolhead.homed_axes != "xyz" %} + { abort_page_switch() } + {% do set_variable("line1", "") %} + {% do set_variable("line2", "Homing...") %} + {% do set_variable("line3", "") %} + {% do set_variable("line4", "") %} + {% do set_variable("wait_return", "leveling_manual") %} + G28 + {% do start_routine("show_wait_screen") %} + {% else %} + BED_MESH_PROFILE SAVE=t5uid1_mesh_backup + BED_MESH_CLEAR + {% endif %} +run_as_gcode: true + +[t5uid1_routine __leveling_manual_leave] +trigger: leave +page: leveling_manual +script: + BED_MESH_PROFILE LOAD=t5uid1_mesh_backup + BED_MESH_PROFILE REMOVE=t5uid1_mesh_backup +run_as_gcode: true + +[t5uid1_routine __filament] +trigger: enter_pre +page: filament +script: + {% set t5uid1 = printer.t5uid1 %} + {% do set_variable("filament_extruder", t5uid1.constants.extruder_current) %} + {% do set_variable("filament_length", 10) %} + +[t5uid1_routine __pid] +trigger: enter_pre +page: pid +script: + {% set t5uid1 = printer.t5uid1 %} + {% do set_variable("pid_heater", t5uid1.constants.heater_h0) %} + {% do set_variable("pid_temp", t5uid1.constants.temp_pla.hotend) %} + +[t5uid1_routine __info] +trigger: enter_pre +page: info +script: {% do set_variable("debug_count", 0) %} diff --git a/klippy/extras/t5uid1/dgus_reloaded/vars_in.cfg b/klippy/extras/t5uid1/dgus_reloaded/vars_in.cfg new file mode 100644 index 00000000..1ef34515 --- /dev/null +++ b/klippy/extras/t5uid1/dgus_reloaded/vars_in.cfg @@ -0,0 +1,706 @@ +[t5uid1_var __switch_page] +type: input +address: 0x2000 +data_type: uint16 +script: {% do switch_page(page_name(data)) %} + +[t5uid1_var __switch_page_idle] +type: input +address: 0x2002 +data_type: uint16 +script: + {% if not is_busy() %} + {% do switch_page(page_name(data)) %} + {% endif %} + +[t5uid1_var __switch_page_printing] +type: input +address: 0x2003 +data_type: uint16 +script: + {% if printer.t5uid1.is_printing %} + {% do switch_page(page_name(data)) %} + {% endif %} + +[t5uid1_var __status_abort] +type: input +address: 0x2007 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.popup_confirmed %} + {% if t5uid1.is_printing %} + RESPOND TYPE=command MSG=action:cancel + {% endif %} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __status_pause] +type: input +address: 0x2008 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.popup_confirmed %} + {% if t5uid1.is_printing and not printer.pause_resume.is_paused %} + RESPOND TYPE=command MSG=action:pause + {% endif %} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __status_resume] +type: input +address: 0x2009 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.popup_confirmed %} + {% if t5uid1.is_printing and printer.pause_resume.is_paused %} + RESPOND TYPE=command MSG=action:resume + {% endif %} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __adjust_set_feedrate] +type: input +address: 0x200a +data_type: int16 +script: + M220 S{data} + {% do start_routine("trigger_full_update") %} +run_as_gcode: true + +[t5uid1_var __adjust_set_flowrate] +type: input +address: 0x200b +data_type: int16 +script: + M221 S{data} + {% do start_routine("trigger_full_update") %} +run_as_gcode: true + +[t5uid1_var __adjust_set_offset] +type: input +address: 0x200e +data_type: int16 +script: + {% if "z" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set offset = data|float / 10 ** 2 %} + SET_GCODE_OFFSET Z={offset} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __adjust_offset] +type: input +address: 0x200f +data_type: uint16 +script: + {% if "z" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.adjust_increment %} + SET_GCODE_OFFSET Z_ADJUST=0.01 + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.adjust_decrement %} + SET_GCODE_OFFSET Z_ADJUST=-0.01 + {% do start_routine("trigger_full_update") %} + {% endif %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __temp_preset] +type: input +address: 0x2010 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.preset_pla %} + SET_HEATER_TEMPERATURE HEATER={printer.toolhead.extruder} TARGET={t5uid1.constants.temp_pla.hotend} + SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={t5uid1.constants.temp_pla.bed} + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.preset_abs %} + SET_HEATER_TEMPERATURE HEATER={printer.toolhead.extruder} TARGET={t5uid1.constants.temp_abs.hotend} + SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={t5uid1.constants.temp_abs.bed} + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.preset_petg %} + SET_HEATER_TEMPERATURE HEATER={printer.toolhead.extruder} TARGET={t5uid1.constants.temp_petg.hotend} + SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={t5uid1.constants.temp_petg.bed} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __temp_set_target_bed] +type: input +address: 0x2011 +data_type: int16 +script: + {% set max_temp = heater_max_temp("heater_bed", 10)|round|int %} + SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET={[data, max_temp]|min} + {% do start_routine("trigger_full_update") %} +run_as_gcode: true + +[t5uid1_var __temp_set_target_h0] +type: input +address: 0x2012 +data_type: int16 +script: + {% set max_temp = heater_max_temp("extruder", 15)|round|int %} + SET_HEATER_TEMPERATURE HEATER=extruder TARGET={[data, max_temp]|min} + {% do start_routine("trigger_full_update") %} +run_as_gcode: true + +[t5uid1_var __temp_set_target_h1] +type: input +address: 0x2013 +data_type: int16 +script: + {% if 'extruder1' in printer %} + {% set max_temp = heater_max_temp("extruder1", 15)|round|int %} + SET_HEATER_TEMPERATURE HEATER=extruder1 TARGET={[data, max_temp]|min} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __temp_cool] +type: input +address: 0x2014 +data_type: int16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.heater_all %} + SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=0 + SET_HEATER_TEMPERATURE HEATER=extruder TARGET=0 + {% if 'extruder1' in printer %} + SET_HEATER_TEMPERATURE HEATER=extruder1 TARGET=0 + {% endif %} + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.heater_bed %} + SET_HEATER_TEMPERATURE HEATER=heater_bed TARGET=0 + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.heater_h0 %} + SET_HEATER_TEMPERATURE HEATER=extruder TARGET=0 + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.heater_h1 %} + {% if 'extruder1' in printer %} + SET_HEATER_TEMPERATURE HEATER=extruder1 TARGET=0 + {% do start_routine("trigger_full_update") %} + {% endif %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __stepper_control] +type: input +address: 0x2015 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.stepper_enable %} + {% set steppers = ['stepper_x', 'stepper_y', 'stepper_z', 'extruder'] %} + {% if 'extruder1' in printer %} + {% do steppers.append('extruder1') %} + {% endif %} + {% for stepper in steppers %} + SET_STEPPER_ENABLE STEPPER={stepper} ENABLE=1 + {% endfor %} + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.stepper_disable %} + M18 + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __level_offset_set] +type: input +address: 0x2016 +data_type: int16 +script: + {% if "z" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set offset = data|float / 10 ** 2 %} + SET_GCODE_OFFSET Z={offset} + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __level_offset_step] +type: input +address: 0x2017 +data_type: uint16 +script: + {% if "z" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% set offset_steps = get_variable("offset_steps", + t5uid1.constants['step_size_0.1']) %} + {% if offset_steps == t5uid1.constants['step_size_0.1'] %} + {% set step = "0.1" %} + {% elif offset_steps == t5uid1.constants['step_size_0.01'] %} + {% set step = "0.01" %} + {% endif %} + {% if data == t5uid1.constants.adjust_increment %} + SET_GCODE_OFFSET Z_ADJUST={step} + {% do start_routine("trigger_full_update") %} + {% elif data == t5uid1.constants.adjust_decrement %} + SET_GCODE_OFFSET Z_ADJUST=-{step} + {% do start_routine("trigger_full_update") %} + {% endif %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __level_offset_set_step] +type: input +address: 0x2018 +data_type: uint16 +script: + {% do set_variable("offset_steps", data) %} + {% do start_routine("trigger_full_update") %} + +[t5uid1_var __level_manual_point] +type: input +address: 0x2019 +data_type: uint16 +script: + {% if printer.toolhead.homed_axes != "xyz" %} + {% do set_message("Homing required") %} + {% elif is_busy() %} + {% do set_message("Busy") %} + {% else %} + {% set inset = 30 %} + {% set hop = 5 %} + {% set t5uid1 = printer.t5uid1 %} + {% if data == 1 %} + {% set x = (t5uid1.limits.x_max - t5uid1.limits.x_min) / 2 %} + {% set y = (t5uid1.limits.y_max - t5uid1.limits.y_min) / 2 %} + {% elif data == 2 %} + {% set x = t5uid1.limits.x_min + inset %} + {% set y = t5uid1.limits.y_min + inset %} + {% elif data == 3 %} + {% set x = t5uid1.limits.x_max - inset %} + {% set y = t5uid1.limits.y_min + inset %} + {% elif data == 4 %} + {% set x = t5uid1.limits.x_max - inset %} + {% set y = t5uid1.limits.y_max - inset %} + {% elif data == 5 %} + {% set x = t5uid1.limits.x_min + inset %} + {% set y = t5uid1.limits.y_max - inset %} + {% endif %} + SAVE_GCODE_STATE NAME=state_level_manual_point + G90 + {% if printer.gcode.move_zpos < hop %} + G1 Z{hop} F600 + {% endif %} + G1 X{x} Y{y} F4800 + G1 Z0 F300 + RESTORE_GCODE_STATE NAME=state_level_manual_point + {% endif %} +run_as_gcode: true + +[t5uid1_var __level_auto_probe] +type: input +address: 0x201a +data_type: none +script: + {% if printer.toolhead.homed_axes != "xyz" %} + {% do set_message("Homing required") %} + {% elif is_busy() %} + {% do set_message("Busy") %} + {% else %} + BED_MESH_CALIBRATE + G28 Z + {% do start_routine("show_probing_screen") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __level_auto_disable] +type: input +address: 0x201b +data_type: none +script: + {% if is_busy() %} + {% do set_message("Busy") %} + {% else %} + BED_MESH_CLEAR + BED_MESH_PROFILE REMOVE=default + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __filament_select] +type: input +address: 0x201c +data_type: int16 +script: + {% do set_variable("filament_extruder", data) %} + {% do start_routine("trigger_full_update") %} + +[t5uid1_var __filament_set_length] +type: input +address: 0x201d +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set extruder = get_variable("filament_extruder", + t5uid1.constants.extruder_current) %} + {% if extruder == t5uid1.constants.extruder_current %} + {% set len = limit_extrude(printer.toolhead.extruder, data) %} + {% elif extruder == t5uid1.constants.extruder_e0 %} + {% set len = limit_extrude("extruder", data) %} + {% elif extruder == t5uid1.constants.extruder_e1 %} + {% set len = limit_extrude("extruder1", data) %} + {% endif %} + {% do set_variable("filament_length", len|round|int) %} + {% do start_routine("trigger_full_update") %} + +[t5uid1_var __filament_move] +type: input +address: 0x201e +data_type: uint16 +script: + {% if is_busy() %} + {% do set_message("Busy") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% set extruder = get_variable("filament_extruder", + t5uid1.constants.extruder_current) %} + {% if extruder == t5uid1.constants.extruder_current %} + {% set ext_name = printer.toolhead.extruder %} + {% elif extruder == t5uid1.constants.extruder_e0 %} + {% set ext_name = "extruder" %} + {% elif extruder == t5uid1.constants.extruder_e1 %} + {% set ext_name = "extruder1" %} + {% endif %} + {% if ext_name not in printer %} + {% do set_message("Invalid extruder") %} + {% elif printer[ext_name].temperature < heater_min_extrude_temp(ext_name) %} + {% do set_message("Temperature too low") %} + {% else %} + SAVE_GCODE_STATE NAME=state_filament_move + ACTIVATE_EXTRUDER EXTRUDER={ext_name} + M83 + {% set length = get_variable("filament_length", 10) %} + {% if data == t5uid1.constants.filament_retract %} + G1 E-{length} + {% elif data == t5uid1.constants.filament_extrude %} + G1 E{length} + {% endif %} + RESTORE_GCODE_STATE NAME=state_filament_move + {% endif %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __home] +type: input +address: 0x201f +data_type: uint16 +script: + {% if is_busy() %} + {% do set_message("Busy") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% do set_variable("line1", "") %} + {% do set_variable("line2", "Homing...") %} + {% do set_variable("line3", "") %} + {% do set_variable("line4", "") %} + {% do set_variable("wait_return", t5uid1.page) %} + {% if data == t5uid1.constants.axis_xyz %} + G28 X Y Z + {% elif data == t5uid1.constants.axis_xy %} + G28 X Y + {% elif data == t5uid1.constants.axis_z %} + G28 Z + {% endif %} + {% do start_routine("show_wait_screen") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __move_x] +type: input +address: 0x2020 +data_type: int16 +script: + {% if "x" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% set pos = data|float / 10 ** 1 %} + {% if pos < t5uid1.limits.x_min %} + {% set pos = t5uid1.limits.x_min %} + {% elif pos > t5uid1.limits.x_max %} + {% set pos = t5uid1.limits.x_max %} + {% endif %} + SAVE_GCODE_STATE NAME=state_move_x + G90 + G1 X{pos} F4800 + RESTORE_GCODE_STATE NAME=state_move_x + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __move_y] +type: input +address: 0x2021 +data_type: int16 +script: + {% if "y" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% set pos = data|float / 10 ** 1 %} + {% if pos < t5uid1.limits.y_min %} + {% set pos = t5uid1.limits.y_min %} + {% elif pos > t5uid1.limits.y_max %} + {% set pos = t5uid1.limits.y_max %} + {% endif %} + SAVE_GCODE_STATE NAME=state_move_y + G90 + G1 Y{pos} F4800 + RESTORE_GCODE_STATE NAME=state_move_y + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __move_z] +type: input +address: 0x2022 +data_type: int16 +script: + {% if "z" not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% set pos = data|float / 10 ** 1 %} + {% if pos < t5uid1.limits.z_min %} + {% set pos = t5uid1.limits.z_min %} + {% elif pos > t5uid1.limits.z_max %} + {% set pos = t5uid1.limits.z_max %} + {% endif %} + SAVE_GCODE_STATE NAME=state_move_z + G90 + G1 Z{pos} F900 + RESTORE_GCODE_STATE NAME=state_move_z + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __move_step] +type: input +address: 0x2023 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set move_steps = get_variable("move_steps", + t5uid1.constants.step_size_10) %} + {% if move_steps == t5uid1.constants.step_size_10 %} + {% set step = 10.0 %} + {% elif move_steps == t5uid1.constants.step_size_1 %} + {% set step = 1.0 %} + {% elif move_steps == t5uid1.constants['step_size_0.1'] %} + {% set step = 0.1 %} + {% endif %} + {% if data == t5uid1.constants['move_x+'] %} + {% set axis = "x" %} + {% set pos = printer.gcode.move_xpos + step %} + {% set speed = 4800 %} + {% elif data == t5uid1.constants['move_x-'] %} + {% set axis = "x" %} + {% set pos = printer.gcode.move_xpos - step %} + {% set speed = 4800 %} + {% elif data == t5uid1.constants['move_y+'] %} + {% set axis = "y" %} + {% set pos = printer.gcode.move_ypos + step %} + {% set speed = 4800 %} + {% elif data == t5uid1.constants['move_y-'] %} + {% set axis = "y" %} + {% set pos = printer.gcode.move_ypos - step %} + {% set speed = 4800 %} + {% elif data == t5uid1.constants['move_z+'] %} + {% set axis = "z" %} + {% set pos = printer.gcode.move_zpos + step %} + {% set speed = 900 %} + {% elif data == t5uid1.constants['move_z-'] %} + {% set axis = "z" %} + {% set pos = printer.gcode.move_zpos - step %} + {% set speed = 900 %} + {% endif %} + {% if axis not in printer.toolhead.homed_axes %} + {% do set_message("Homing required") %} + {% else %} + {% if pos < t5uid1.limits[axis ~ "_min"] %} + {% set pos = t5uid1.limits[axis ~ "_min"] %} + {% elif pos > t5uid1.limits[axis ~ "_max"] %} + {% set pos = t5uid1.limits[axis ~ "_max"] %} + {% endif %} + SAVE_GCODE_STATE NAME=state_move_step + G90 + G1 {axis|upper}{pos} F{speed} + RESTORE_GCODE_STATE NAME=state_move_step + {% do start_routine("trigger_full_update") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __move_set_step] +type: input +address: 0x2024 +data_type: uint16 +script: + {% do set_variable("move_steps", data) %} + {% do start_routine("trigger_full_update") %} + +[t5uid1_var __settings2_extra] +type: input +address: 0x2028 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.extra_button1 %} + {% if t5uid1.has_bltouch %} + {% if is_busy() %} + {% do set_message("Busy") %} + {% else %} + {% do start_routine("reset_bltouch") %} + {% endif %} + {% else %} + {% do switch_page("info") %} + {% endif %} + {% elif data == t5uid1.constants.extra_button2 %} + {% if t5uid1.has_bltouch %} + {% do switch_page("info") %} + {% endif %} + {% endif %} + +[t5uid1_var __pid_select] +type: input +address: 0x2029 +data_type: int16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if data == t5uid1.constants.heater_bed %} + {% do set_variable("pid_temp", t5uid1.constants.temp_pla.bed) %} + {% do set_variable("pid_heater", data) %} + {% elif data == t5uid1.constants.heater_h0 %} + {% do set_variable("pid_temp", t5uid1.constants.temp_pla.hotend) %} + {% do set_variable("pid_heater", data) %} + {% elif data == t5uid1.constants.heater_h1 %} + {% do set_variable("pid_temp", t5uid1.constants.temp_pla.hotend) %} + {% do set_variable("pid_heater", data) %} + {% endif %} + {% do start_routine("trigger_full_update") %} + +[t5uid1_var __pid_set_temp] +type: input +address: 0x202a +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set pid_heater = get_variable("pid_heater", + t5uid1.constants.heater_h0) %} + {% if pid_heater == t5uid1.constants.heater_bed %} + {% set min_temp = heater_min_temp("heater_bed")|round|int %} + {% set max_temp = heater_max_temp("heater_bed", 10)|round|int %} + {% elif pid_heater == t5uid1.constants.heater_h0 %} + {% set min_temp = heater_min_temp("extruder")|round|int %} + {% set max_temp = heater_max_temp("extruder", 15)|round|int %} + {% elif pid_heater == t5uid1.constants.heater_h1 %} + {% set min_temp = heater_min_temp("extruder1")|round|int %} + {% set max_temp = heater_max_temp("extruder1", 15)|round|int %} + {% endif %} + {% do set_variable("pid_temp", [([data, max_temp]|min), min_temp]|max) %} + {% do start_routine("trigger_full_update") %} + +[t5uid1_var __pid_run] +type: input +address: 0x202b +data_type: none +script: + {% if is_busy() %} + {% do set_message("Busy") %} + {% else %} + {% set t5uid1 = printer.t5uid1 %} + {% set pid_heater = get_variable("pid_heater", + t5uid1.constants.heater_h0) %} + {% set pid_temp = get_variable("pid_temp", + t5uid1.constants.temp_pla.hotend) %} + {% do set_variable("line1", "") %} + {% do set_variable("line2", "PID autotuning...") %} + {% do set_variable("line3", "") %} + {% do set_variable("line4", "") %} + {% do set_variable("wait_return", "pid") %} + {% do set_variable("wait_return_sound", t5uid1.notification_sound) %} + {% if pid_heater == t5uid1.constants.heater_bed %} + PID_CALIBRATE HEATER=heater_bed TARGET={pid_temp} + {% elif pid_heater == t5uid1.constants.heater_h0 %} + PID_CALIBRATE HEATER=extruder TARGET={pid_temp} + {% elif pid_heater == t5uid1.constants.heater_h1 %} + {% if 'extruder1' in printer %} + PID_CALIBRATE HEATER=extruder1 TARGET={pid_temp} + {% endif %} + {% endif %} + {% do start_routine("show_wait_screen") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __save_config] +type: input +address: 0x2030 +data_type: none +script: + {% if is_busy() %} + {% do set_message("Busy") %} + {% else %} + {% do set_variable("line1", "") %} + {% do set_variable("line2", "Saving configuration...") %} + {% do set_variable("line3", "") %} + {% do set_variable("line4", "") %} + {% do set_variable("wait_return", "boot") %} + SAVE_CONFIG + {% do start_routine("show_wait_screen") %} + {% endif %} +run_as_gcode: true + +[t5uid1_var __fan_speed] +type: input +address: 0x4000 +data_type: uint16 +script: + {% if data >= 0 and data <= 100 %} + M106 S{(data|float / 100 * 255)|round|int} + {% endif %} +run_as_gcode: true + +[t5uid1_var __volume] +type: input +address: 0x4022 +data_type: uint16 +script: + {% if data >= 0 and data <= 100 %} + {% do set_volume(data) %} + {% endif %} + +[t5uid1_var __brightness] +type: input +address: 0x4023 +data_type: uint16 +script: + {% if data >= 0 and data <= 100 %} + {% do set_brightness(data) %} + {% endif %} + +[t5uid1_var __debug] +type: input +address: 0x5001 +data_type: none +script: + {% set counter = get_variable("debug_count", 0)|int + 1 %} + {% do set_variable("debug_count", counter) %} + {% if counter >= 10 %} + {% do switch_page("debug1") %} + {% endif %} diff --git a/klippy/extras/t5uid1/dgus_reloaded/vars_out.cfg b/klippy/extras/t5uid1/dgus_reloaded/vars_out.cfg new file mode 100644 index 00000000..edce3cc8 --- /dev/null +++ b/klippy/extras/t5uid1/dgus_reloaded/vars_out.cfg @@ -0,0 +1,461 @@ +[t5uid1_var line1] +type: output +address: 0x1100 +data_type: str +data_len: 32 +script: { "{:^32s}".format(get_variable("line1", "")|trim) } + +[t5uid1_var line2] +type: output +address: 0x1120 +data_type: str +data_len: 32 +script: { "{:^32s}".format(get_variable("line2", "")|trim) } + +[t5uid1_var line3] +type: output +address: 0x1140 +data_type: str +data_len: 32 +script: { "{:^32s}".format(get_variable("line3", "")|trim) } + +[t5uid1_var line4] +type: output +address: 0x1160 +data_type: str +data_len: 32 +script: { "{:^32s}".format(get_variable("line4", "")|trim) } + +[t5uid1_var message] +type: output +address: 0x3000 +data_type: str +data_len: 32 +script: { "{:>32s}".format(get_variable("message", "")|trim) } + +[t5uid1_var status_position_z] +type: output +address: 0x30e6 +data_type: int16 +script: { (printer.gcode.move_zpos * 10 ** 1)|round|int } + +[t5uid1_var status_ellapsed] +type: output +address: 0x30e7 +data_type: str +data_len: 15 +script: { get_duration(printer.t5uid1.print_duration) } + +[t5uid1_var status_percent] +type: output +address: 0x30f6 +data_type: uint16 +script: { printer.t5uid1.print_progress } + +[t5uid1_var status_icons] +type: output +address: 0x30f7 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if not t5uid1.is_printing %} + {% do disable_control(t5uid1.pages.print_status, + t5uid1.control_types.popup_window, + t5uid1.controls.pause) %} + {% do disable_control(t5uid1.pages.print_status, + t5uid1.control_types.popup_window, + t5uid1.controls.resume) %} + { "0" } + {% elif printer.pause_resume.is_paused %} + {% do enable_control(t5uid1.pages.print_status, + t5uid1.control_types.popup_window, + t5uid1.controls.resume) %} + {% do disable_control(t5uid1.pages.print_status, + t5uid1.control_types.popup_window, + t5uid1.controls.pause) %} + { t5uid1.constants.status_icon_resume } + {% else %} + {% do enable_control(t5uid1.pages.print_status, + t5uid1.control_types.popup_window, + t5uid1.controls.pause) %} + {% do disable_control(t5uid1.pages.print_status, + t5uid1.control_types.popup_window, + t5uid1.controls.resume) %} + { t5uid1.constants.status_icon_pause } + {% endif %} + +[t5uid1_var adjust_feedrate] +type: output +address: 0x30f8 +data_type: int16 +script: { (printer.gcode.speed_factor * 100)|round|int } + +[t5uid1_var adjust_flowrate] +type: output +address: 0x30f9 +data_type: int16 +script: { (printer.gcode.extrude_factor * 100)|round|int } + +[t5uid1_var temp_bed_current] +type: output +address: 0x30fc +data_type: int16 +script: { printer.heater_bed.temperature|round|int } + +[t5uid1_var temp_bed_target] +type: output +address: 0x30fd +data_type: int16 +script: { printer.heater_bed.target|round|int } + +[t5uid1_var temp_bed_max] +type: output +address: 0x30fe +data_type: uint16 +script: { heater_max_temp("heater_bed", 10)|round|int } + +[t5uid1_var temp_h0_current] +type: output +address: 0x30ff +data_type: int16 +script: { printer.extruder.temperature|round|int } + +[t5uid1_var temp_h0_target] +type: output +address: 0x3100 +data_type: int16 +script: { printer.extruder.target|round|int } + +[t5uid1_var temp_h0_max] +type: output +address: 0x3101 +data_type: uint16 +script: { heater_max_temp("extruder", 15)|round|int } + +[t5uid1_var temp_h1_current] +type: output +address: 0x3102 +data_type: int16 +script: + {% if 'extruder1' in printer %} + { printer.extruder1.temperature|round|int } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var temp_h1_target] +type: output +address: 0x3103 +data_type: int16 +script: + {% if 'extruder1' in printer %} + { printer.extruder1.target|round|int } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var temp_h1_max] +type: output +address: 0x3104 +data_type: uint16 +script: + {% if 'extruder1' in printer %} + { heater_max_temp("extruder1", 15)|round|int } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var stepper_status] +type: output +address: 0x3105 +data_type: uint16 +script: + {% if all_steppers_enabled() %} + { printer.t5uid1.constants.enabled } + {% else %} + { printer.t5uid1.constants.disabled } + {% endif %} + +[t5uid1_var level_offset] +type: output +address: 0x3106 +data_type: int16 +script: { (printer.gcode.base_zpos * 10 ** 1)|round|int } + +[t5uid1_var level_offset_step_icons] +type: output +address: 0x3107 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set offset_steps = get_variable("offset_steps", + t5uid1.constants['step_size_0.1']) %} + {% if offset_steps == t5uid1.constants['step_size_0.1'] %} + { t5uid1.constants['step_icon_0.1'] } + {% elif offset_steps == t5uid1.constants['step_size_0.01'] %} + { t5uid1.constants['step_icon_0.01'] } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var level_auto_disable_icon] +type: output +address: 0x3108 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if printer.bed_mesh.profile_name != "" %} + {% do enable_control(t5uid1.pages.leveling_automatic, + t5uid1.control_types.return_key_code, + t5uid1.controls.disable) %} + { t5uid1.constants.enabled } + {% else %} + {% do disable_control(t5uid1.pages.leveling_automatic, + t5uid1.control_types.return_key_code, + t5uid1.controls.disable) %} + { t5uid1.constants.disabled } + {% endif %} + +[t5uid1_var level_auto_grid] +type: output +address: 0x3109 +data_type: array[int16] +array_len: 25 +script: + {% set grid = printer.bed_mesh.probed_matrix %} + {% if not grid or grid|length < 5 %} + {% set grid = [] %} + {% for i in range(5) %} + {% do grid.append([0, 0, 0, 0, 0]) %} + {% endfor %} + {% endif %} + {% for line in grid %} + {% for val in line %} + { (val * 10 ** 3)|round|int }, + {% endfor %} + {% endfor %} + +[t5uid1_var level_probing_icons] +type: output +address: 0x3122 +data_type: uint32 +script: { probed_matrix() } + +[t5uid1_var filament_icons] +type: output +address: 0x3124 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set extruder = get_variable("filament_extruder", + t5uid1.constants.extruder_current) %} + {% if extruder == t5uid1.constants.extruder_current %} + {% if printer.toolhead.extruder == "extruder" %} + { t5uid1.constants.extruder_icon_e0 } + {% elif printer.toolhead.extruder == "extruder1" %} + { t5uid1.constants.extruder_icon_e1 } + {% else %} + { "0" } + {% endif %} + {% elif extruder == t5uid1.constants.extruder_e0 %} + { t5uid1.constants.extruder_icon_e0 } + {% elif extruder == t5uid1.constants.extruder_e1 %} + { t5uid1.constants.extruder_icon_e1 } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var filament_length] +type: output +address: 0x3125 +data_type: uint16 +script: { get_variable("filament_length", 10) } + +[t5uid1_var move_current_x] +type: output +address: 0x3126 +data_type: int16 +script: { (printer.gcode.move_xpos * 10 ** 1)|round|int } + +[t5uid1_var move_current_y] +type: output +address: 0x3127 +data_type: int16 +script: { (printer.gcode.move_ypos * 10 ** 1)|round|int } + +[t5uid1_var move_current_z] +type: output +address: 0x3128 +data_type: int16 +script: { (printer.gcode.move_zpos * 10 ** 1)|round|int } + +[t5uid1_var move_step_icons] +type: output +address: 0x3129 +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set move_steps = get_variable("move_steps", + t5uid1.constants.step_size_10) %} + {% if move_steps == t5uid1.constants.step_size_10 %} + { t5uid1.constants.step_icon_10 } + {% elif move_steps == t5uid1.constants.step_size_1 %} + { t5uid1.constants.step_icon_1 } + {% elif move_steps == t5uid1.constants['step_size_0.1'] %} + { t5uid1.constants['step_icon_0.1'] } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var bltouch] +type: output +address: 0x312a +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% if t5uid1.has_bltouch %} + {% do enable_control(t5uid1.pages.settings_menu2, + t5uid1.control_types.return_key_code, + t5uid1.controls.extra2) %} + { t5uid1.constants.enabled } + {% else %} + {% do disable_control(t5uid1.pages.settings_menu2, + t5uid1.control_types.return_key_code, + t5uid1.controls.extra2) %} + { t5uid1.constants.disabled } + {% endif %} + +[t5uid1_var pid_heater_icons] +type: output +address: 0x312b +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set pid_heater = get_variable("pid_heater", + t5uid1.constants.heater_h0) %} + {% if pid_heater == t5uid1.constants.heater_bed %} + { t5uid1.constants.heater_icon_bed } + {% elif pid_heater == t5uid1.constants.heater_h0 %} + { t5uid1.constants.heater_icon_h0 } + {% elif pid_heater == t5uid1.constants.heater_h1 %} + { t5uid1.constants.heater_icon_h1 } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var pid_temp] +type: output +address: 0x312c +data_type: uint16 +script: { get_variable("pid_temp", printer.t5uid1.constants.temp_pla.hotend) } + +[t5uid1_var pid_kp] +type: output +address: 0x312d +data_type: int32 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set pid_heater = get_variable("pid_heater", + t5uid1.constants.heater_h0) %} + {% if pid_heater == t5uid1.constants.heater_bed %} + { (pid_param("heater_bed", "p") * 10 ** 2)|round|int } + {% elif pid_heater == t5uid1.constants.heater_h0 %} + { (pid_param("extruder", "p") * 10 ** 2)|round|int } + {% elif pid_heater == t5uid1.constants.heater_h1 %} + { (pid_param("extruder1", "p") * 10 ** 2)|round|int } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var pid_ki] +type: output +address: 0x312f +data_type: int32 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set pid_heater = get_variable("pid_heater", + t5uid1.constants.heater_h0) %} + {% if pid_heater == t5uid1.constants.heater_bed %} + { (pid_param("heater_bed", "i") * 10 ** 2)|round|int } + {% elif pid_heater == t5uid1.constants.heater_h0 %} + { (pid_param("extruder", "i") * 10 ** 2)|round|int } + {% elif pid_heater == t5uid1.constants.heater_h1 %} + { (pid_param("extruder1", "i") * 10 ** 2)|round|int } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var pid_kd] +type: output +address: 0x3131 +data_type: int32 +script: + {% set t5uid1 = printer.t5uid1 %} + {% set pid_heater = get_variable("pid_heater", + t5uid1.constants.heater_h0) %} + {% if pid_heater == t5uid1.constants.heater_bed %} + { (pid_param("heater_bed", "d") * 10 ** 2)|round|int } + {% elif pid_heater == t5uid1.constants.heater_h0 %} + { (pid_param("extruder", "d") * 10 ** 2)|round|int } + {% elif pid_heater == t5uid1.constants.heater_h1 %} + { (pid_param("extruder1", "d") * 10 ** 2)|round|int } + {% else %} + { "0" } + {% endif %} + +[t5uid1_var info_machine] +type: output +address: 0x3133 +data_type: str +data_len: 24 +script: { printer.t5uid1.machine_name } + +[t5uid1_var info_build_volume] +type: output +address: 0x314b +data_type: str +data_len: 24 +script: + {% set limits = printer.t5uid1.limits %} + { "{:d}x{:d}x{:d}".format((limits.x_max - limits.x_min)|round(0, 'floor')|int, + (limits.y_max - limits.y_min)|round(0, 'floor')|int, + (limits.z_max - limits.z_min)|round(0, 'floor')|int) } + +[t5uid1_var info_version] +type: output +address: 0x3163 +data_type: str +data_len: 16 +script: { printer.t5uid1.version } + +[t5uid1_var wait_icons] +type: output +address: 0x31bd +data_type: uint16 +script: + {% set t5uid1 = printer.t5uid1 %} + {% do disable_control(t5uid1.pages.wait, + t5uid1.control_types.popup_window, + t5uid1.controls.abort) %} + {% do disable_control(t5uid1.pages.wait, + t5uid1.control_types.return_key_code, + t5uid1.controls.continue) %} + { "0" } + +[t5uid1_var fan_speed] +type: output +address: 0x4000 +data_type: uint16 +script: { (printer.fan.speed * 100)|round|int } + +[t5uid1_var volume] +type: output +address: 0x4022 +data_type: uint16 +script: { printer.t5uid1.volume } + +[t5uid1_var brightness] +type: output +address: 0x4023 +data_type: uint16 +script: { printer.t5uid1.brightness } diff --git a/klippy/extras/t5uid1/page.py b/klippy/extras/t5uid1/page.py new file mode 100644 index 00000000..ffb2e428 --- /dev/null +++ b/klippy/extras/t5uid1/page.py @@ -0,0 +1,38 @@ +# Page class +# +# Copyright (C) 2020 Desuuuu +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +class T5UID1_Page: + def __init__(self, var_names, config): + self.printer = config.get_printer() + name_parts = config.get_name().split() + if len(name_parts) != 2: + raise config.error("Section name '%s' is not valid" + % (config.get_name(),)) + self.name = name_parts[1] + + self.id = config.getint('id', minval=0, maxval=255) + self.is_boot = config.getboolean('boot', False) + self.is_timeout = config.getboolean('timeout', False) + self.is_shutdown = config.getboolean('shutdown', False) + self.var_auto = [] + self.var = [] + + for var in config.get('var_auto', '').split(','): + var = var.strip() + if len(var) > 0 and var not in self.var_auto: + if var not in var_names: + raise config.error("Invalid var '%s' in section '%s'" + % (var, config.get_name())) + self.var_auto.append(var) + + for var in config.get('var', '').split(','): + var = var.strip() + if (len(var) > 0 + and var not in self.var_auto and var not in self.var): + if var not in var_names: + raise config.error("Invalid var '%s' in section '%s'" + % (var, config.get_name())) + self.var.append(var) diff --git a/klippy/extras/t5uid1/routine.py b/klippy/extras/t5uid1/routine.py new file mode 100644 index 00000000..b02163c9 --- /dev/null +++ b/klippy/extras/t5uid1/routine.py @@ -0,0 +1,102 @@ +# Routine class +# +# Copyright (C) 2020 Desuuuu +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +TRIGGERS = [ + "enter_pre", + "enter", + "leave", + "manual" +] + +class T5UID1_Routine: + def __init__(self, gcode_macro, context, page_names, config): + self.printer = config.get_printer() + + self.gcode = self.printer.lookup_object('gcode') + self.reactor = self.printer.get_reactor() + + name_parts = config.get_name().split() + if len(name_parts) != 2: + raise config.error("Section name '%s' is not valid" + % (config.get_name(),)) + self.name = name_parts[1] + + self.trigger = config.get('trigger') + if self.trigger not in TRIGGERS: + raise config.error("Invalid trigger '%s' in section '%s'" + % (self.trigger, config.get_name())) + + if self.trigger != "manual": + self.page = config.get('page') + if self.page not in page_names: + raise config.error("Invalid page '%s' in section '%s'" + % (self.page, config.get_name())) + else: + self.page = None + + if self.trigger != "enter_pre": + self.delay = config.getint('delay', None, minval=0, maxval=60) + else: + self.delay = None + + if self.trigger in ["enter", "manual"]: + self.interval = config.getint('interval', 0, minval=0, maxval=60) + else: + self.interval = 0 + + self._template = gcode_macro.load_template(config, 'script') + self._context = context + self.run_as_gcode = config.getboolean('run_as_gcode', False) + + self._should_stop = False + self._timer = self.reactor.register_timer(self._timer_run) + + def _timer_run(self, eventtime): + if self._should_stop: + return self.reactor.NEVER + self.run(is_timer=True) + if self._should_stop or self.interval <= 0: + return self.reactor.NEVER + return eventtime + self.interval + + def run(self, repeat=True, is_timer=False): + if not is_timer: + self._should_stop = False + if self.delay is not None: + if self.delay > 0: + self.reactor.update_timer(self._timer, + self.reactor.monotonic() + + self.delay) + else: + self.reactor.update_timer(self._timer, self.reactor.NOW) + return + + swrap = self._template.create_status_wrapper() + context = { 'printer': swrap, + 'is_timer': is_timer } + context.update(self._context) + + result = self._template.render(context).strip() + if self.run_as_gcode and len(result) > 0: + self.gcode.run_script_from_command(result) + + if not is_timer and self.interval > 0: + if repeat: + next_run = self.reactor.monotonic() + self.interval + else: + next_run = self.reactor.NEVER + self.reactor.update_timer(self._timer, next_run) + return + + if self.trigger == "enter_pre": + if len(result) == 0: + return True + commands = [ c.strip() for c in result.split('\n') ] + return not "DGUS_ABORT_PAGE_SWITCH" in commands + + def stop(self): + self._should_stop = True + self.reactor.update_timer(self._timer, self.reactor.NEVER) diff --git a/klippy/extras/t5uid1/t5uid1.py b/klippy/extras/t5uid1/t5uid1.py new file mode 100644 index 00000000..be45ed46 --- /dev/null +++ b/klippy/extras/t5uid1/t5uid1.py @@ -0,0 +1,891 @@ +# Support for DGUS T5UID1 touchscreens +# +# Copyright (C) 2020 Desuuuu +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import os, logging, struct, textwrap +import jinja2 +import mcu +from . import var, page, routine, dgus_reloaded +from .. import gcode_macro, heaters + +T5UID1_firmware_cfg = { + 'dgus_reloaded': dgus_reloaded.configuration +} + +DEFAULT_VOLUME = 75 +DEFAULT_BRIGHTNESS = 100 + +T5UID1_CMD_WRITEVAR = 0x82 +T5UID1_CMD_READVAR = 0x83 + +T5UID1_ADDR_VERSION = 0x0f +T5UID1_ADDR_BRIGHTNESS = 0x82 +T5UID1_ADDR_PAGE = 0x84 +T5UID1_ADDR_SOUND = 0xa0 +T5UID1_ADDR_VOLUME = 0xa1 +T5UID1_ADDR_CONTROL = 0xb0 + +TIMEOUT_SECS = 15 +CMD_DELAY = 0.02 + +CONTROL_TYPES = { + 'variable_data_input': 0x00, + 'popup_window': 0x01, + 'incremental_adjust': 0x02, + 'slider_adjust': 0x03, + 'rtc_settings': 0x04, + 'return_key_code': 0x05, + 'text_input': 0x06, + 'firmware_settings': 0x07 +} + +def map_value_range(x, in_min, in_max, out_min, out_max): + return int(round((x - in_min) + * (out_max - out_min) + / (in_max - in_min) + + out_min)) + +def get_duration(seconds): + if type(seconds) is not int: + seconds = int(seconds) + if seconds < 0: + seconds = 0 + minutes = seconds / 60 + hours = minutes / 60 + days = hours / 24 + days %= 365 + hours %= 24 + minutes %= 60 + seconds %= 60 + result = str(seconds) + "s" + if minutes: + result = str(minutes) + "m " + result + if hours: + result = str(hours) + "h " + result + if days: + result = str(days) + "d " + result + return result + +def bitwise_and(lhs, rhs): + return lhs & rhs + +def bitwise_or(lhs, rhs): + return lhs | rhs + +class T5UID1GCodeMacro: + def __init__(self, config): + self.printer = config.get_printer() + self.env = jinja2.Environment('{%', '%}', '{', '}', + trim_blocks=True, + lstrip_blocks=True, + extensions=['jinja2.ext.do']) + def load_template(self, config, option, default=None): + name = "%s:%s" % (config.get_name(), option) + if default is None: + script = config.get(option) + else: + script = config.get(option, default) + return gcode_macro.TemplateWrapper(self.printer, self.env, name, script) + +class T5UID1: + def __init__(self, config): + self.printer = config.get_printer() + self.name = config.get_name() + + self.reactor = self.printer.get_reactor() + + self.gcode = self.printer.lookup_object('gcode') + self.configfile = self.printer.lookup_object('configfile') + + self.stepper_enable = self.heaters = self.bed_mesh = None + self.toolhead = self.probe = self.pause_resume = None + self.extruders = {} + + self.mcu = mcu.get_printer_mcu(self.printer, + config.get('t5uid1_mcu', 'mcu')) + self.oid = self.mcu.create_oid() + + self._version = self.printer.get_start_args().get('software_version') + + self._gcode_macro = T5UID1GCodeMacro(config) + + firmware_cfg = config.getchoice('firmware', T5UID1_firmware_cfg) + self._firmware = config.get('firmware') + + self._machine_name = config.get('machine_name', 'Generic 3D printer') + self._baud = config.getint('baud', 115200, minval=1200, maxval=921600) + self._update_interval = config.getint('update_interval', 2, + minval=1, maxval=10) + self._volume = config.getint('volume', DEFAULT_VOLUME, + minval=0, maxval=100) + self._brightness = config.getint('brightness', DEFAULT_BRIGHTNESS, + minval=0, maxval=100) + self._boot_sound = config.getint('boot_sound', + firmware_cfg['boot_sound'], + minval=-1, maxval=255) + self._notification_sound = config.getint('notification_sound', + firmware_cfg['notification_sound'], minval=-1, maxval=255) + + self._z_min = config.getfloat('z_min', None) + self._z_max = config.getfloat('z_max', None) + + self._last_cmd_time = 0 + self._gui_version = 0 + self._os_version = 0 + self._current_page = "" + self._variable_data = {} + self._status_data = {} + self._vars = {} + self._pages = {} + self._routines = {} + self._is_printing = False + self._print_progress = 0 + self._print_start_time = -1 + self._print_pause_time = -1 + self._print_end_time = -1 + self._boot_page = self._timeout_page = self._shutdown_page = None + self._t5uid1_ping_cmd = self._t5uid1_write_cmd = None + self._is_connected = False + + global_context = { + 'get_variable': self.get_variable, + 'set_variable': self.set_variable, + 'enable_control': self.enable_control, + 'disable_control': self.disable_control, + 'start_routine': self.start_routine, + 'stop_routine': self.stop_routine, + 'set_message': self.set_message, + 'bitwise_and': bitwise_and, + 'bitwise_or': bitwise_or + } + + context_input = dict(global_context) + context_input.update({ + 'page_name': self.page_name, + 'switch_page': self.switch_page, + 'play_sound': self.play_sound, + 'set_volume': self.set_volume, + 'set_brightness': self.set_brightness, + 'limit_extrude': self.limit_extrude, + 'heater_min_temp': self.heater_min_temp, + 'heater_max_temp': self.heater_max_temp, + 'heater_min_extrude_temp': self.heater_min_extrude_temp, + 'is_busy': self.is_busy + }) + + context_output = dict(global_context) + context_output.update({ + 'all_steppers_enabled': self.all_steppers_enabled, + 'heater_min_temp': self.heater_min_temp, + 'heater_max_temp': self.heater_max_temp, + 'probed_matrix': self.probed_matrix, + 'pid_param': self.pid_param, + 'get_duration': get_duration + }) + + context_routine = dict(global_context) + context_routine.update({ + 'page_name': self.page_name, + 'switch_page': self.switch_page, + 'play_sound': self.play_sound, + 'set_volume': self.set_volume, + 'set_brightness': self.set_brightness, + 'abort_page_switch': self.abort_page_switch, + 'full_update': self.full_update, + 'is_busy': self.is_busy, + 'check_paused': self.check_paused + }) + + self._status_data.update({ + 'controls': firmware_cfg['controls'], + 'constants': firmware_cfg['constants'] + }) + + self._load_config(config, + firmware_cfg['config_files'], + context_input, + context_output, + context_routine) + + if self._boot_page is None: + raise self.printer.config_error("No boot page found") + if self._timeout_page is None: + self._timeout_page = self._boot_page + if self._shutdown_page is None: + self._shutdown_page = self._boot_page + + self.mcu.register_config_callback(self._build_config) + + self._update_timer = self.reactor.register_timer(self._send_update) + self._ping_timer = self.reactor.register_timer(self._do_ping) + + self.gcode.register_command( + 'DGUS_ABORT_PAGE_SWITCH', self.cmd_DGUS_ABORT_PAGE_SWITCH) + self.gcode.register_command( + 'DGUS_PLAY_SOUND', self.cmd_DGUS_PLAY_SOUND) + self.gcode.register_command( + 'DGUS_PRINT_START', self.cmd_DGUS_PRINT_START) + self.gcode.register_command( + 'DGUS_PRINT_END', self.cmd_DGUS_PRINT_END) + self.gcode.register_command('M73', self.cmd_M73) + self.gcode.register_command('M117', self.cmd_M117) + self.gcode.register_command('M300', self.cmd_M300) + + self.printer.register_event_handler("klippy:ready", + self._handle_ready) + self.printer.register_event_handler("klippy:shutdown", + self._handle_shutdown) + self.printer.register_event_handler("klippy:disconnect", + self._handle_disconnect) + + def _load_config(self, config, fnames, ctx_in, ctx_out, ctx_routine): + if type(fnames) is not list: + fnames = [fnames] + v_list = config.get_prefix_sections('t5uid1_var ') + v_main_names = { c.get_name(): 1 for c in v_list } + p_list = config.get_prefix_sections('t5uid1_page ') + p_main_names = { c.get_name(): 1 for c in p_list } + r_list = config.get_prefix_sections('t5uid1_routine ') + r_main_names = { c.get_name(): 1 for c in r_list } + for fname in fnames: + filepath = os.path.join(os.path.dirname(__file__), + self._firmware, + fname) + try: + dconfig = self.configfile.read_config(filepath) + except Exception: + raise self.printer.config_error("Cannot load config '%s'" + % (filepath,)) + v_list += [c for c in dconfig.get_prefix_sections('t5uid1_var ') + if c.get_name() not in v_main_names] + p_list += [c for c in dconfig.get_prefix_sections('t5uid1_page ') + if c.get_name() not in p_main_names] + r_list += [c for c in dconfig.get_prefix_sections('t5uid1_routine ') + if c.get_name() not in r_main_names] + for c in v_list: + v = var.T5UID1_Var(self._gcode_macro, + ctx_in, + ctx_out, + c) + if v.name in self._vars: + raise self.printer.config_error("t5uid1_var '%s' already" + " exists" % (v.name,)) + self._vars[v.name] = v + for c in p_list: + p = page.T5UID1_Page(self._vars.keys(), c) + if p.name in self._pages: + raise self.printer.config_error("t5uid1_page '%s' already" + " exists" % (p.name,)) + self._pages[p.name] = p + if p.is_boot: + if self._boot_page is None: + self._boot_page = p.name + else: + raise self.printer.config_error("Multiple boot pages" + " found") + if p.is_timeout: + if self._timeout_page is None: + self._timeout_page = p.name + else: + raise self.printer.config_error("Multiple timeout pages" + " found") + if p.is_shutdown: + if self._shutdown_page is None: + self._shutdown_page = p.name + else: + raise self.printer.config_error("Multiple shutdown pages" + " found") + for c in r_list: + r = routine.T5UID1_Routine(self._gcode_macro, + ctx_routine, + self._pages.keys(), + c) + if r.name in self._routines: + raise self.printer.config_error("t5uid1_routine '%s' already" + " exists" % (r.name,)) + self._routines[r.name] = r + + def _build_config(self): + timeout_command, timeout_data = self.switch_page(self._timeout_page, + send=False) + timeout_data = "".join(["%02x" % (x,) for x in timeout_data]) + + self.mcu.add_config_cmd( + "config_t5uid1 oid=%d baud=%d timeout=%d" + " timeout_command=%d timeout_data=%s" + % (self.oid, self._baud, TIMEOUT_SECS, + timeout_command, timeout_data)) + + curtime = self.reactor.monotonic() + self._last_cmd_time = self.mcu.estimated_print_time(curtime) + + cmd_queue = self.mcu.alloc_command_queue() + self._t5uid1_ping_cmd = self.mcu.lookup_command( + "t5uid1_ping oid=%c", cq=cmd_queue) + self._t5uid1_write_cmd = self.mcu.lookup_command( + "t5uid1_write oid=%c command=%c data=%*s", cq=cmd_queue) + + self.mcu.register_response(self._handle_t5uid1_received, + "t5uid1_received") + + def _handle_ready(self): + has_bltouch = False + try: + self.printer.lookup_object('bltouch') + has_bltouch = True + except Exception: + pass + self._status_data.update({ + 'limits': self.limits(), + 'has_bltouch': has_bltouch + }) + self._is_connected = True + self.reactor.register_timer(self._on_ready, self.reactor.NOW) + + def _on_ready(self, eventtime): + if not self._is_connected: + return self.reactor.NEVER + self._last_cmd_time = self.mcu.estimated_print_time(eventtime) + self.t5uid1_command_read(T5UID1_ADDR_VERSION, 1) + self.set_brightness(self._brightness) + self.switch_page(self._boot_page) + if self._boot_sound >= 0: + self.play_sound(self._boot_sound, volume=self._volume) + else: + self.set_volume(self._volume) + return self.reactor.NEVER + + def _handle_shutdown(self): + msg = getattr(self.mcu, "_shutdown_msg", "").strip() + parts = textwrap.wrap(msg, 32) + while len(parts) < 4: + parts.append("") + self.set_variable("line1", parts[0].strip()) + self.set_variable("line2", parts[1].strip()) + self.set_variable("line3", parts[2].strip()) + self.set_variable("line4", parts[3].strip()) + self.switch_page(self._shutdown_page) + if self._notification_sound >= 0: + self.play_sound(self._notification_sound) + + def _handle_disconnect(self): + self._is_connected = False + self._current_page = "" + self.reactor.update_timer(self._update_timer, self.reactor.NEVER) + self.reactor.update_timer(self._ping_timer, self.reactor.NEVER) + + def _handle_t5uid1_received(self, params): + if not self._is_connected: + return + logging.debug("t5uid1_received %s", params) + if params['command'] != T5UID1_CMD_READVAR: + return + data = bytearray(params['data']) + if len(data) < 3: + logging.warning("Received invalid T5UID1 message") + return + address = struct.unpack(">H", data[:2])[0] + data_len = data[2] << 1 + if len(data) < data_len + 3: + logging.warning("Received invalid T5UID1 message") + return + data = data[3:data_len + 3] + self.reactor.register_async_callback( + (lambda e, s=self, a=address, d=data: s.handle_received(a, d))) + + def handle_received(self, address, data): + if not self._is_connected: + return + if address == T5UID1_ADDR_VERSION and len(data) == 2: + self._gui_version = data[0] + self._os_version = data[1] + return + handled = False + for name in self._vars: + if (self._vars[name].address != address + or self._vars[name].type != "input"): + continue + handled = True + try: + self._vars[name].data_received(data) + except ValueError as e: + logging.exception("Exception in '%s' receive handler: %s" + % (name, str(e))) + except Exception as e: + logging.exception("Unhandled exception in '%s' receive" + " handler: %s" % (name, str(e))) + if not handled: + logging.info("Received unhandled T5UID1 message for address %s" + % (hex(address),)) + + def send_var(self, name): + if name not in self._vars: + raise Exception("T5UID1_Var '%s' not found" % (name,)) + return self.t5uid1_command_write(self._vars[name].address, + self._vars[name].prepare_data()) + + def page_name(self, page_id): + if type(page_id) is not int: + page_id = int(page_id) + for name in self._pages: + if self._pages[name].id == page_id: + return name + raise Exception("T5UID1_Page %d not found" % (page_id,)) + + def send_page_vars(self, page=None, complete=False): + if page is None: + page = self._current_page + if page not in self._pages: + raise Exception("T5UID1_Page '%s' not found" % (page,)) + if complete: + for var_name in self._pages[page].var: + self.send_var(var_name) + for var_name in self._pages[page].var_auto: + self.send_var(var_name) + + def full_update(self): + self.send_page_vars(complete=True) + self.reactor.update_timer(self._update_timer, + self.reactor.monotonic() + + self._update_interval) + + def start_routine(self, routine): + if routine not in self._routines: + raise Exception("T5UID1_Routine '%s' not found" % (routine,)) + if self._routines[routine].trigger != "manual": + raise Exception("T5UID1_Routine '%s' cannot be started manually" + % (routine,)) + self._routines[routine].run() + + def stop_routine(self, routine): + if routine not in self._routines: + raise Exception("T5UID1_Routine '%s' not found" % (routine,)) + self._routines[routine].stop() + + def _start_page_routines(self, page, trigger): + if page not in self._pages: + raise Exception("T5UID1_Page '%s' not found" % (page,)) + results = [] + for routine in self._routines: + if (self._routines[routine].page != page + or self._routines[routine].trigger != trigger): + continue + result = self._routines[routine].run() + if result is not None: + results.append(result) + return all(results) + + def _stop_page_routines(self, page): + if page not in self._pages: + raise Exception("T5UID1_Page '%s' not found" % (page,)) + for routine in self._routines: + if self._routines[routine].page != page: + continue + self._routines[routine].stop() + + class sentinel: pass + + def get_variable(self, name, default=sentinel): + if name not in self._variable_data: + if default is not self.sentinel: + return default + raise Exception("Variable '%s' not found" % (name,)) + return self._variable_data[name] + + def set_variable(self, name, value): + self._variable_data[name] = value + + def check_paused(self): + if not self._is_printing: + return + if self.pause_resume is None: + try: + self.pause_resume = self.printer.lookup_object('pause_resume') + except Exception: + return + curtime = self.reactor.monotonic() + if self._print_pause_time < 0 and self.pause_resume.is_paused: + self._print_pause_time = curtime + elif self._print_pause_time >= 0 and not self.pause_resume.is_paused: + pause_duration = curtime - self._print_pause_time + if pause_duration > 0: + self._print_start_time += pause_duration + self._print_pause_time = -1 + + def get_status(self, eventtime): + pages = { p: self._pages[p].id for p in self._pages } + res = dict(self._status_data) + if not self._is_printing: + print_duration = self._print_end_time - self._print_start_time + elif self._print_pause_time >= 0: + print_duration = self._print_pause_time - self._print_start_time + else: + print_duration = eventtime - self._print_start_time + res.update({ + 'version': self._version, + 'machine_name': self._machine_name, + 'gui_version': self._gui_version, + 'os_version': self._os_version, + 'notification_sound': self._notification_sound, + 'page': self._current_page, + 'volume': self._volume, + 'brightness': self._brightness, + 'pages': pages, + 'control_types': CONTROL_TYPES, + 'is_printing': self._is_printing, + 'print_progress': self._print_progress, + 'print_duration': max(0, print_duration) + }) + return res + + def _send_update(self, eventtime): + if not self._is_connected or not self._current_page: + return self.reactor.NEVER + try: + self.send_page_vars(self._current_page, complete=False) + except Exception as e: + logging.exception("Unhandled exception in update timer: %s" + % (name, str(e))) + return eventtime + self._update_interval + + def _do_ping(self, eventtime): + if not self._is_connected or self._t5uid1_ping_cmd is None: + return self.reactor.NEVER + print_time = self.mcu.estimated_print_time(eventtime) + print_time = max(self._last_cmd_time + CMD_DELAY, print_time) + clock = self.mcu.print_time_to_clock(print_time) + self._t5uid1_ping_cmd.send([self.oid], minclock=clock) + self._last_cmd_time = print_time + return eventtime + TIMEOUT_SECS - 2 + + def _t5uid1_write(self, command, data, schedule_ping=True): + if not self._is_connected or self._t5uid1_write_cmd is None: + return + curtime = self.reactor.monotonic() + print_time = self.mcu.estimated_print_time(curtime) + print_time = max(self._last_cmd_time + CMD_DELAY, print_time) + clock = self.mcu.print_time_to_clock(print_time) + self._t5uid1_write_cmd.send([self.oid, command, list(data)], + minclock=clock) + self._last_cmd_time = print_time + if schedule_ping: + self.reactor.update_timer(self._ping_timer, + curtime + TIMEOUT_SECS - 2) + + def t5uid1_command_write(self, address, data, send=True): + if address < 0 or address > 0xffff: + raise ValueError("invalid address") + if type(data) is not bytearray: + raise ValueError("invalid data") + if len(data) < 1 or len(data) > 64 or len(data) % 2 != 0: + raise ValueError("invalid data length") + command = T5UID1_CMD_WRITEVAR + command_data = bytearray([ (address >> 8), (address & 0xff) ]) + command_data.extend(data) + if not send: + return (command, command_data) + self._t5uid1_write(command, command_data) + + def t5uid1_command_read(self, address, wlen, send=True): + if address < 0 or address > 0xffff: + raise ValueError("invalid address") + if wlen < 1 or wlen > 0x7d: + raise ValueError("invalid wlen") + command = T5UID1_CMD_READVAR + command_data = bytearray([ (address >> 8), (address & 0xff), wlen ]) + if not send: + return (command, command_data) + self._t5uid1_write(command, command_data) + + def switch_page(self, name, send=True): + if name not in self._pages: + raise ValueError("invalid page") + if not send: + return self.t5uid1_command_write(T5UID1_ADDR_PAGE, + bytearray([ + 0x5a, 0x01, + 0x00, self._pages[name].id + ]), + send) + if name == self._current_page: + return + if not self._start_page_routines(name, "enter_pre"): + return + self.send_page_vars(name, complete=True) + self.t5uid1_command_write(T5UID1_ADDR_PAGE, + bytearray([ + 0x5a, 0x01, + 0x00, self._pages[name].id + ]), + send) + if self._current_page: + self._stop_page_routines(self._current_page) + self._start_page_routines(self._current_page, "leave") + self._current_page = name + self._start_page_routines(name, "enter") + self.reactor.update_timer(self._update_timer, + self.reactor.monotonic() + + self._update_interval) + + def abort_page_switch(self): + return "DGUS_ABORT_PAGE_SWITCH" + + def play_sound(self, start, slen=1, volume=-1, send=True): + if start < 0 or start > 255: + raise ValueError("invalid start") + if slen < 1 or slen > 255: + raise ValueError("invalid slen") + if volume > 100: + raise ValueError("invalid volume") + if volume < 0: + volume = self._volume + val = map_value_range(volume, 0, 100, 0, 255) + return self.t5uid1_command_write(T5UID1_ADDR_SOUND, + bytearray([start, slen, val, 0]), + send) + + def enable_control(self, page, ctype, control, send=True): + if page < 0 or page > 255: + raise ValueError("invalid page") + if ctype < 0 or ctype > 255: + raise ValueError("invalid ctype") + if control < 0 or control > 255: + raise ValueError("invalid control") + return self.t5uid1_command_write(T5UID1_ADDR_CONTROL, + bytearray([ + 0x5a, 0xa5, 0, page, + control, ctype, 0, 0x01 + ]), + send) + + def disable_control(self, page, ctype, control, send=True): + if page < 0 or page > 255: + raise ValueError("invalid page") + if ctype < 0 or ctype > 255: + raise ValueError("invalid ctype") + if control < 0 or control > 255: + raise ValueError("invalid control") + return self.t5uid1_command_write(T5UID1_ADDR_CONTROL, + bytearray([ + 0x5a, 0xa5, 0, page, + control, ctype, 0, 0 + ]), + send) + + def set_brightness(self, brightness, send=True): + if brightness < 0 or brightness > 100: + raise ValueError("invalid brightness") + val = map_value_range(brightness, 0, 100, 5, 100) + result = self.t5uid1_command_write(T5UID1_ADDR_BRIGHTNESS, + bytearray([val, val]), + send) + if not send: + return result + if self._brightness != brightness: + self._brightness = brightness + self.configfile.set(self.name, 'brightness', brightness) + + def set_volume(self, volume, send=True): + if volume < 0 or volume > 100: + raise ValueError("invalid volume") + val = map_value_range(volume, 0, 100, 0, 255) + result = self.t5uid1_command_write(T5UID1_ADDR_VOLUME, + bytearray([val, 0]), + send) + if not send: + return result + if self._volume != volume: + self._volume = volume + self.configfile.set(self.name, 'volume', volume) + + def all_steppers_enabled(self): + if self.stepper_enable is None: + self.stepper_enable = self.printer.lookup_object('stepper_enable') + res = True + for name in ['stepper_x', 'stepper_y', 'stepper_z']: + res &= self.stepper_enable.lookup_enable(name).is_motor_enabled() + return res + + def heater_min_temp(self, heater): + if self.heaters is None: + self.heaters = self.printer.lookup_object('heaters') + try: + return self.heaters.lookup_heater(heater).min_temp + except Exception: + return 0 + + def heater_max_temp(self, heater, margin=0): + if self.heaters is None: + self.heaters = self.printer.lookup_object('heaters') + try: + return max(0, self.heaters.lookup_heater(heater).max_temp - margin) + except Exception: + return 0 + + def heater_min_extrude_temp(self, heater): + if self.heaters is None: + self.heaters = self.printer.lookup_object('heaters') + return self.heaters.lookup_heater(heater).min_extrude_temp + + def probed_matrix(self): + if self.bed_mesh is None: + self.bed_mesh = self.printer.lookup_object('bed_mesh') + count = len(self.bed_mesh.bmc.probe_helper.results) + points_map = [ 0, 1, 2, 3, 4, + 9, 8, 7, 6, 5, + 10, 11, 12, 13, 14, + 19, 18, 17, 16, 15, + 20, 21, 22, 23, 24] + res = 0 + for i in range(25): + if count > points_map[i]: + if i < 16: + res |= 1 << (i + 16) + else: + res |= 1 << (i - 16) + return res + + def pid_param(self, heater, param): + if param not in ['p', 'i', 'd']: + raise ValueError("Invalid param") + if self.heaters is None: + self.heaters = self.printer.lookup_object('heaters') + try: + return getattr(self.heaters.lookup_heater(heater).control, + 'K' + param) * heaters.PID_PARAM_BASE + except Exception: + return 0 + + def limit_extrude(self, extruder, val): + try: + if extruder in self.extruders: + res = self.extruders[extruder].max_e_dist + else: + e = self.printer.lookup_object(extruder) + res = e.max_e_dist + self.extruders[extruder] = e + return min(res, val) + except Exception: + return 0 + + def limits(self): + if self.toolhead is None: + self.toolhead = self.printer.lookup_object('toolhead') + kin = self.toolhead.get_kinematics() + x_min, x_max = kin.rails[0].get_range() + y_min, y_max = kin.rails[1].get_range() + z_min, z_max = kin.rails[2].get_range() + if (self._z_min is not None + and self._z_min > z_min + and self._z_min < z_max): + z_min = self._z_min + if (self._z_max is not None + and self._z_max < z_max + and self._z_max > z_min): + z_max = self._z_max + return { + 'x_min': x_min, + 'x_max': x_max, + 'y_min': y_min, + 'y_max': y_max, + 'z_min': z_min, + 'z_max': z_max + } + + def set_message(self, message): + self.set_variable('message', message) + if 'message' in self._vars: + self.send_var('message') + if len(message) > 0 and 'message_timeout' in self._routines: + self.start_routine('message_timeout') + + def is_busy(self): + if self.toolhead is None: + self.toolhead = self.printer.lookup_object('toolhead') + if self.probe is None: + self.probe = self.printer.lookup_object('probe') + eventtime = self.reactor.monotonic() + print_time, est_print_time, lookahead_empty = self.toolhead.check_busy( + eventtime) + idle_time = est_print_time - print_time + return (not lookahead_empty + or idle_time < 1.0 + or self.gcode.get_mutex().test() + or self.probe.multi_probe_pending) + + def cmd_DGUS_ABORT_PAGE_SWITCH(self, gcmd): + pass + + def cmd_DGUS_PLAY_SOUND(self, gcmd): + if self._notification_sound >= 0: + start = gcmd.get_int('START', self._notification_sound, + minval=0, maxval=255) + else: + start = gcmd.get_int('START', minval=0, maxval=255) + slen = gcmd.get_int('LEN', 1, minval=0, maxval=255) + volume = gcmd.get_int('VOLUME', -1, minval=0, maxval=100) + try: + self.play_sound(start, slen, volume) + except Exception as e: + raise gcmd.error(str(e)) + gcmd.respond_info("Playing sound %d (len=%d, volume=%d)" + % (start, slen, volume)) + + def cmd_DGUS_PRINT_START(self, gcmd): + self._print_progress = 0 + self._print_start_time = self.reactor.monotonic() + self._print_pause_time = -1 + self._print_end_time = -1 + self._is_printing = True + self.check_paused() + if 'print_start' in self._routines: + self.start_routine('print_start') + + def cmd_DGUS_PRINT_END(self, gcmd): + if not self._is_printing: + return + self._print_progress = 100 + curtime = self.reactor.monotonic() + if self._print_pause_time >= 0: + pause_duration = curtime - self._print_pause_time + if pause_duration > 0: + self._print_start_time += pause_duration + self._print_pause_time = -1 + self._print_end_time = curtime + self._is_printing = False + if 'print_end' in self._routines: + self.start_routine('print_end') + + def cmd_M73(self, gcmd): + progress = gcmd.get_int('P', 0) + self._print_progress = min(100, max(0, progress)) + + def cmd_M117(self, gcmd): + msg = gcmd.get_commandline() + umsg = msg.upper() + if not umsg.startswith('M117'): + start = umsg.find('M117') + end = msg.rfind('*') + msg = msg[start:end] + if len(msg) > 5: + self.set_message(msg[5:]) + else: + self.set_message("") + + def cmd_M300(self, gcmd): + if self._notification_sound >= 0: + start = gcmd.get_int('S', self._notification_sound) + else: + start = gcmd.get_int('S', minval=0, maxval=255) + slen = gcmd.get_int('P', 1, minval=1, maxval=255) + volume = gcmd.get_int('V', -1, minval=0, maxval=100) + if start < 0 or start > 255: + start = self._notification_sound + try: + self.play_sound(start, slen, volume) + except Exception as e: + raise gcmd.error(str(e)) + +def load_config(config): + return T5UID1(config) diff --git a/klippy/extras/t5uid1/var.py b/klippy/extras/t5uid1/var.py new file mode 100644 index 00000000..b09d4b02 --- /dev/null +++ b/klippy/extras/t5uid1/var.py @@ -0,0 +1,154 @@ +# Variable class +# +# Copyright (C) 2020 Desuuuu +# +# This file may be distributed under the terms of the GNU GPLv3 license. +import struct + +TYPES_LEN = { + 'int16': 2, + 'uint16': 2, + 'int32': 4, + 'uint32': 4, + 'float': 4 +} + +TYPES_FMT = { + 'int16': '>h', + 'uint16': '>H', + 'int32': '>i', + 'uint32': '>I', + 'float': '>f' +} + +class T5UID1_Var: + def __init__(self, gcode_macro, input_context, output_context, config): + self.printer = config.get_printer() + + self.gcode = self.printer.lookup_object('gcode') + + name_parts = config.get_name().split() + if len(name_parts) != 2: + raise config.error("Section name '%s' is not valid" + % (config.get_name(),)) + self.name = name_parts[1] + + self.type = config.get('type') + if self.type not in ["input", "output"]: + raise config.error("Invalid type '%s' in section '%s'" + % (self.type, config.get_name())) + + address = config.get('address') + try: + self.address = int(address, 16) + if self.address < 0x1000 or self.address > 0xffff: + raise + except: + raise config.error("Invalid address '%s' in section '%s'" + % (address, config.get_name())) + + data_types = TYPES_LEN.keys() + data_types.append('str') + if self.type == "input": + data_types.append('none') + + self.data_type = config.get('data_type') + if (self.data_type.startswith('array[') + and self.data_type.endswith(']') + and self.type == "output" + and self.data_type != "str"): + self.data_type = self.data_type[6:-1] + self.array_len = config.getint('array_len', minval=2, maxval=32) + else: + self.array_len = 1 + if self.data_type not in data_types: + raise config.error("Invalid data_type '%s' in section '%s'" + % (self.data_type, config.get_name())) + + if self.data_type == "none": + self.data_len = 0 + elif self.data_type == "str": + self.data_len = config.getint('data_len', minval=1, maxval=32) + else: + self.data_len = TYPES_LEN[self.data_type] + + if self.type == "input": + self._template = gcode_macro.load_template(config, 'script') + self._context = input_context + self.run_as_gcode = config.getboolean('run_as_gcode', False) + elif self.type == "output": + self._template = gcode_macro.load_template(config, 'script') + self._context = output_context + self.run_as_gcode = False + + def data_received(self, data): + if self.type != "input": + raise Exception("not an input") + + swrap = self._template.create_status_wrapper() + context = { 'printer': swrap } + context.update(self._context) + + if self.data_type != "none" and self.data_len != 0: + received_len = len(data) + if self.data_type == "str": + buf = bytearray() + for i in range(received_len): + if i >= self.data_len: + break + if data[i] == 0x00: + break + if (i + 1 < received_len + and data[i] == 0xff and data[i + 1] == 0xff): + break + buf.append(data[i]) + data = str(buf.decode('ascii')) + elif received_len != self.data_len: + raise ValueError("Expected %d bytes, got %d" + % (self.data_len, received_len)) + else: + data = struct.unpack(TYPES_FMT[self.data_type], data)[0] + context.update({ 'data': data }) + + result = self._template.render(context).strip() + if self.run_as_gcode and len(result) > 0: + self.gcode.run_script_from_command(result) + + def prepare_data(self): + if self.type != "output": + raise Exception("not an output") + + swrap = self._template.create_status_wrapper() + context = { 'printer': swrap } + context.update(self._context) + result = self._template.render(context) + + if self.data_type == "str": + result = bytearray(result.replace('\n', '')) + target_len = self.data_len + if target_len % 2 != 0: + target_len += 1 + extra_bytes = len(result) - target_len + if extra_bytes > 0: + result = result[:target_len] + elif extra_bytes < 0: + result.extend([0] * abs(extra_bytes)) + else: + parts = result.strip().split(',') + result = bytearray() + count = 0 + for part in parts: + part = part.strip() + if len(part) == 0: + continue + if self.data_type == "float": + part = float(part) + else: + part = int(part) + result.extend(struct.pack(TYPES_FMT[self.data_type], part)) + count += 1 + if count != self.array_len: + raise ValueError("Expected %d values, got %d" + % (self.array_len, count)) + + return result diff --git a/src/avr/Kconfig b/src/avr/Kconfig index e430b246..4ab4b054 100644 --- a/src/avr/Kconfig +++ b/src/avr/Kconfig @@ -102,6 +102,34 @@ config AVR_STACK_SIZE config AVR_WATCHDOG bool default y +config T5UID1_SERIAL + depends on MACH_atmega2560 + bool "Enable DGUS T5UID1 screen" + default n +choice + depends on T5UID1_SERIAL + prompt "Screen Serial Port" if LOW_LEVEL_OPTIONS + default T5UID1_SERIAL_UART2 + help + Select the serial device to use for the touchscreen. + config T5UID1_SERIAL_UART0 + bool "UART0" + config T5UID1_SERIAL_UART1 + bool "UART1" + config T5UID1_SERIAL_UART2 + bool "UART2" + config T5UID1_SERIAL_UART3 + bool "UART3" +endchoice + +config T5UID1_SERIAL_PORT + depends on T5UID1_SERIAL + int + default 3 if T5UID1_SERIAL_UART3 + default 1 if T5UID1_SERIAL_UART1 + default 0 if T5UID1_SERIAL_UART0 + default 2 + config USBSERIAL bool "Use USB for communication (instead of serial)" depends on MACH_at90usb1286 || MACH_at90usb646 || MACH_atmega32u4 diff --git a/src/avr/Makefile b/src/avr/Makefile index c9315b99..937b74b3 100644 --- a/src/avr/Makefile +++ b/src/avr/Makefile @@ -17,6 +17,8 @@ src-$(CONFIG_HAVE_GPIO_HARD_PWM) += avr/hard_pwm.c src-$(CONFIG_AVR_WATCHDOG) += avr/watchdog.c src-$(CONFIG_USBSERIAL) += avr/usbserial.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += avr/serial.c generic/serial_irq.c +src-$(CONFIG_T5UID1_SERIAL) += avr/serial_t5uid1.c +src-$(CONFIG_T5UID1_SERIAL) += generic/t5uid1_irq.c # Suppress broken "misspelled signal handler" warnings on gcc 4.8.1 CFLAGS_klipper.elf := $(CFLAGS_klipper.elf) $(if $(filter 4.8.1, $(shell $(CC) -dumpversion)), -w) diff --git a/src/avr/serial_t5uid1.c b/src/avr/serial_t5uid1.c new file mode 100644 index 00000000..504555c5 --- /dev/null +++ b/src/avr/serial_t5uid1.c @@ -0,0 +1,93 @@ +// AVR serial port code. +// +// Copyright (C) 2016-2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // USART_RX_vect +#include "autoconf.h" // CONFIG_T5UID1_SERIAL_PORT +#include "board/t5uid1_irq.h" // t5uid1_rx_byte +#include "command.h" // DECL_CONSTANT_STR +#include "sched.h" // DECL_INIT + +#if CONFIG_T5UID1_SERIAL_PORT == CONFIG_SERIAL_PORT + #error "The serial port selected for the T5UID1 screen is already used" +#endif + +// Reserve serial pins +#if CONFIG_T5UID1_SERIAL_PORT == 0 + #if CONFIG_MACH_atmega1280 || CONFIG_MACH_atmega2560 +DECL_CONSTANT_STR("RESERVE_PINS_t5uid1", "PE0,PE1"); + #else +DECL_CONSTANT_STR("RESERVE_PINS_t5uid1", "PD0,PD1"); + #endif +#elif CONFIG_T5UID1_SERIAL_PORT == 1 +DECL_CONSTANT_STR("RESERVE_PINS_t5uid1", "PD2,PD3"); +#elif CONFIG_T5UID1_SERIAL_PORT == 2 +DECL_CONSTANT_STR("RESERVE_PINS_t5uid1", "PH0,PH1"); +#else +DECL_CONSTANT_STR("RESERVE_PINS_t5uid1", "PJ0,PJ1"); +#endif + +// Helper macros for defining serial port aliases +#define AVR_SERIAL_REG1(prefix, id, suffix) prefix ## id ## suffix +#define AVR_SERIAL_REG(prefix, id, suffix) AVR_SERIAL_REG1(prefix, id, suffix) + +// Serial port register aliases +#define UCSRxA AVR_SERIAL_REG(UCSR, CONFIG_T5UID1_SERIAL_PORT, A) +#define UCSRxB AVR_SERIAL_REG(UCSR, CONFIG_T5UID1_SERIAL_PORT, B) +#define UCSRxC AVR_SERIAL_REG(UCSR, CONFIG_T5UID1_SERIAL_PORT, C) +#define UBRRx AVR_SERIAL_REG(UBRR, CONFIG_T5UID1_SERIAL_PORT,) +#define UDRx AVR_SERIAL_REG(UDR, CONFIG_T5UID1_SERIAL_PORT,) +#define UCSZx1 AVR_SERIAL_REG(UCSZ, CONFIG_T5UID1_SERIAL_PORT, 1) +#define UCSZx0 AVR_SERIAL_REG(UCSZ, CONFIG_T5UID1_SERIAL_PORT, 0) +#define U2Xx AVR_SERIAL_REG(U2X, CONFIG_T5UID1_SERIAL_PORT,) +#define RXENx AVR_SERIAL_REG(RXEN, CONFIG_T5UID1_SERIAL_PORT,) +#define TXENx AVR_SERIAL_REG(TXEN, CONFIG_T5UID1_SERIAL_PORT,) +#define RXCIEx AVR_SERIAL_REG(RXCIE, CONFIG_T5UID1_SERIAL_PORT,) +#define UDRIEx AVR_SERIAL_REG(UDRIE, CONFIG_T5UID1_SERIAL_PORT,) + +#if defined(USART_RX_vect) +// The atmega168 / atmega328 doesn't have an ID in the irq names +#define USARTx_RX_vect USART_RX_vect +#define USARTx_UDRE_vect USART_UDRE_vect +#else +#define USARTx_RX_vect \ + AVR_SERIAL_REG(USART, CONFIG_T5UID1_SERIAL_PORT, _RX_vect) +#define USARTx_UDRE_vect \ + AVR_SERIAL_REG(USART, CONFIG_T5UID1_SERIAL_PORT, _UDRE_vect) +#endif + +void +t5uid1_init(uint32_t baud) +{ + UCSRxA = CONFIG_SERIAL_BAUD_U2X ? (1< +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include // memmove +#include "basecmd.h" // oid_alloc +#include "board/io.h" // readb +#include "board/irq.h" // irq_save +#include "board/misc.h" // timer_read_time +#include "command.h" // DECL_CONSTANT +#include "sched.h" // sched_wake_task +#include "t5uid1_irq.h" // t5uid1_enable_tx_irq + +#define T5UID1_HEADER1 0x5A +#define T5UID1_HEADER2 0xA5 +#define T5UID1_HEADER_LEN 3 + +#define RX_BUFFER_SIZE 192 +#define TIMER_MS 500 + +struct t5uid1 { + struct timer timer; + uint32_t baud; + uint16_t ticks; + uint16_t timeout; + uint8_t timeout_command; + uint8_t timeout_data_len; + uint8_t timeout_data[]; +}; + +static uint8_t receive_buf[RX_BUFFER_SIZE], receive_pos; +static uint8_t transmit_buf[96], transmit_pos, transmit_max; + +static struct task_wake t5uid1_wake; + +void +t5uid1_send_command(uint_fast8_t command, uint8_t *data, uint_fast8_t data_len) +{ + if (data_len < 1) + return; + + // Verify space for message + uint_fast8_t tpos = readb(&transmit_pos), tmax = readb(&transmit_max); + if (tpos >= tmax) { + tpos = tmax = 0; + writeb(&transmit_max, 0); + writeb(&transmit_pos, 0); + } + uint_fast8_t msglen = T5UID1_HEADER_LEN + 1 + data_len; + if (tmax + msglen > sizeof(transmit_buf)) { + if (tmax + msglen - tpos > sizeof(transmit_buf)) + // Not enough space for message + return; + // Disable TX irq and move buffer + writeb(&transmit_max, 0); + tpos = readb(&transmit_pos); + tmax -= tpos; + memmove(&transmit_buf[0], &transmit_buf[tpos], tmax); + writeb(&transmit_pos, 0); + writeb(&transmit_max, tmax); + t5uid1_enable_tx_irq(); + } + + // Generate message + transmit_buf[tmax] = T5UID1_HEADER1; + transmit_buf[tmax + 1] = T5UID1_HEADER2; + transmit_buf[tmax + 2] = data_len + 1; + transmit_buf[tmax + 3] = command; + memcpy(&transmit_buf[tmax + 4], data, data_len); + + // Start message transmit + writeb(&transmit_max, tmax + msglen); + t5uid1_enable_tx_irq(); +} + +static uint_fast8_t +t5uid1_timeout_event(struct timer *timer) +{ + struct t5uid1 *t = container_of(timer, struct t5uid1, timer); + if (++t->ticks >= t->timeout) { + if (t->timeout) { + t5uid1_send_command(t->timeout_command, + t->timeout_data, t->timeout_data_len); + } + t->ticks = UINT16_MAX; + return SF_DONE; + } + t->timer.waketime += timer_from_us(TIMER_MS * 1000); + return SF_RESCHEDULE; +} + +void +reset_timer(struct t5uid1 *t) +{ + if (!t->timeout) { + sched_del_timer(&t->timer); + } else if (t->ticks == UINT16_MAX) { + sched_del_timer(&t->timer); + uint32_t now = timer_read_time(); + t->timer.waketime = now + timer_from_us(TIMER_MS * 1000); + t->timer.func = t5uid1_timeout_event; + if (!timer_is_before(t->timer.waketime, now)) { + t->ticks = 0; + sched_add_timer(&t->timer); + } + } else { + t->ticks = 0; + } +} + +void +command_config_t5uid1(uint32_t *args) +{ + uint8_t timeout_data_len = args[4]; + struct t5uid1 *t = oid_alloc( + args[0], command_config_t5uid1, sizeof(*t) + timeout_data_len); + t->baud = args[1]; + t->ticks = UINT16_MAX; + t->timeout = args[2] * (1000 / TIMER_MS); + t->timeout_command = args[3]; + t->timeout_data_len = timeout_data_len; + uint8_t *timeout_data = (void*)(size_t)args[5]; + memcpy(t->timeout_data, timeout_data, timeout_data_len); + t5uid1_init(t->baud); + reset_timer(t); +} +DECL_COMMAND(command_config_t5uid1, + "config_t5uid1 oid=%c baud=%u timeout=%hu" + " timeout_command=%c timeout_data=%*s"); + +void +command_t5uid1_ping(uint32_t *args) +{ + struct t5uid1 *t = oid_lookup(args[0], command_config_t5uid1); + reset_timer(t); +} +DECL_COMMAND_FLAGS(command_t5uid1_ping, HF_IN_SHUTDOWN, + "t5uid1_ping oid=%c"); + +void +command_t5uid1_write(uint32_t *args) +{ + struct t5uid1 *t = oid_lookup(args[0], command_config_t5uid1); + uint_fast8_t command = args[1]; + uint_fast8_t data_len = args[2]; + uint8_t *data = (void*)(size_t)args[3]; + t5uid1_send_command(command, data, data_len); + reset_timer(t); +} +DECL_COMMAND_FLAGS(command_t5uid1_write, HF_IN_SHUTDOWN, + "t5uid1_write oid=%c command=%c data=%*s"); + +// Rx interrupt - store read data +void +t5uid1_rx_byte(uint_fast8_t data) +{ + if (receive_pos > T5UID1_HEADER_LEN) + sched_wake_task(&t5uid1_wake); + if (receive_pos >= sizeof(receive_buf)) + // Serial overflow - ignore it + return; + receive_buf[receive_pos++] = data; +} + +// Tx interrupt - get next byte to transmit +int +t5uid1_get_tx_byte(uint8_t *pdata) +{ + if (transmit_pos >= transmit_max) + return -1; + *pdata = transmit_buf[transmit_pos++]; + return 0; +} + +// Remove from the receive buffer the given number of bytes +static void +t5uid1_pop_input(uint_fast8_t len) +{ + uint_fast8_t copied = 0; + for (;;) { + uint_fast8_t rpos = readb(&receive_pos); + uint_fast8_t needcopy = rpos - len; + if (needcopy) { + memmove(&receive_buf[copied], &receive_buf[copied + len] + , needcopy - copied); + copied = needcopy; + sched_wake_task(&t5uid1_wake); + } + irqstatus_t flag = irq_save(); + if (rpos != readb(&receive_pos)) { + // Raced with irq handler - retry + irq_restore(flag); + continue; + } + receive_pos = needcopy; + irq_restore(flag); + break; + } +} + +// Find the next command +int_fast8_t +t5uid1_find_command(uint8_t *buf, uint_fast8_t buf_len, uint_fast8_t *pop_count) +{ + if (buf_len < T5UID1_HEADER_LEN) + goto need_more_data; + if (buf[0] != T5UID1_HEADER1 || buf[1] != T5UID1_HEADER2) + goto error; + uint_fast8_t cmdlen = buf[2]; + if (cmdlen < 2) + goto error; + if (buf_len - T5UID1_HEADER_LEN < cmdlen) + goto need_more_data; + *pop_count = T5UID1_HEADER_LEN + cmdlen; + return 1; + +need_more_data: + *pop_count = 0; + return 0; +error: + *pop_count = 1; + return -1; +} + +// Process any incoming commands +void +t5uid1_task(void) +{ + if (!sched_check_wake(&t5uid1_wake)) + return; + uint_fast8_t rpos = readb(&receive_pos), pop_count; + int_fast8_t ret = t5uid1_find_command(receive_buf, rpos, &pop_count); + if (ret > 0) { + uint_fast8_t command = receive_buf[T5UID1_HEADER_LEN]; + uint_fast8_t data_len = pop_count - T5UID1_HEADER_LEN - 1; + uint8_t *data = &receive_buf[T5UID1_HEADER_LEN + 1]; + sendf("t5uid1_received command=%c data=%*s", command, data_len, data); + } + if (ret) { + t5uid1_pop_input(pop_count); + } +} +DECL_TASK(t5uid1_task); diff --git a/src/generic/t5uid1_irq.h b/src/generic/t5uid1_irq.h new file mode 100644 index 00000000..d1775e2c --- /dev/null +++ b/src/generic/t5uid1_irq.h @@ -0,0 +1,14 @@ +#ifndef __GENERIC_T5UID1_IRQ_H +#define __GENERIC_T5UID1_IRQ_H + +#include // uint32_t + +void t5uid1_init(uint32_t baud); +// callback provided by board specific code +void t5uid1_enable_tx_irq(void); + +// t5uid1_irq.c +void t5uid1_rx_byte(uint_fast8_t data); +int t5uid1_get_tx_byte(uint8_t *pdata); + +#endif // t5uid1_irq.h diff --git a/src/lpc176x/Kconfig b/src/lpc176x/Kconfig index 1b1d908b..91a8fc30 100644 --- a/src/lpc176x/Kconfig +++ b/src/lpc176x/Kconfig @@ -68,4 +68,9 @@ config SERIAL bool default y +config T5UID1_SERIAL + depends on USBSERIAL + bool "Use UART0 for DGUS T5UID1 screen" + default n + endif diff --git a/src/lpc176x/Makefile b/src/lpc176x/Makefile index b9fd454f..c4de068e 100644 --- a/src/lpc176x/Makefile +++ b/src/lpc176x/Makefile @@ -22,6 +22,8 @@ src-$(CONFIG_HAVE_GPIO_SPI) += lpc176x/spi.c src-$(CONFIG_USBSERIAL) += lpc176x/usbserial.c lpc176x/chipid.c src-$(CONFIG_USBSERIAL) += generic/usb_cdc.c src-$(CONFIG_SERIAL) += lpc176x/serial.c generic/serial_irq.c +src-$(CONFIG_T5UID1_SERIAL) += lpc176x/serial_t5uid1.c +src-$(CONFIG_T5UID1_SERIAL) += generic/t5uid1_irq.c # Build the additional bin output file target-y += $(OUT)klipper.bin diff --git a/src/lpc176x/serial_t5uid1.c b/src/lpc176x/serial_t5uid1.c new file mode 100644 index 00000000..1705be05 --- /dev/null +++ b/src/lpc176x/serial_t5uid1.c @@ -0,0 +1,80 @@ +// lpc176x serial port +// +// Copyright (C) 2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/armcm_boot.h" // armcm_enable_irq +#include "board/irq.h" // irq_save +#include "board/t5uid1_irq.h" // t5uid1_rx_byte +#include "command.h" // DECL_CONSTANT_STR +#include "internal.h" // gpio_peripheral +#include "sched.h" // DECL_INIT + +// Write tx bytes to the serial port +static void +kick_tx(void) +{ + for (;;) { + if (!(LPC_UART0->LSR & (1<<5))) { + // Output fifo full - enable tx irq + LPC_UART0->IER = 0x03; + break; + } + uint8_t data; + int ret = t5uid1_get_tx_byte(&data); + if (ret) { + // No more data to send - disable tx irq + LPC_UART0->IER = 0x01; + break; + } + LPC_UART0->THR = data; + } +} + +void +UART0_IRQHandler(void) +{ + uint32_t iir = LPC_UART0->IIR, status = iir & 0x0f; + if (status == 0x04) + t5uid1_rx_byte(LPC_UART0->RBR); + else if (status == 0x02) + kick_tx(); +} + +void +t5uid1_enable_tx_irq(void) +{ + if (LPC_UART0->LSR & (1<<5)) { + irqstatus_t flag = irq_save(); + kick_tx(); + irq_restore(flag); + } +} + +DECL_CONSTANT_STR("RESERVE_PINS_t5uid1", "P0.3,P0.2"); + +void +t5uid1_init(uint32_t baud) +{ + // Setup baud + LPC_UART0->LCR = (1<<7); // set DLAB bit + enable_pclock(PCLK_UART0); + uint32_t pclk = SystemCoreClock; + uint32_t div = pclk / (baud * 16); + LPC_UART0->DLL = div & 0xff; + LPC_UART0->DLM = (div >> 8) & 0xff; + LPC_UART0->FDR = 0x10; + LPC_UART0->LCR = 3; // 8N1 ; clear DLAB bit + + // Enable fifo + LPC_UART0->FCR = 0x01; + + // Setup pins + gpio_peripheral(GPIO(0, 3), 1, 0); + gpio_peripheral(GPIO(0, 2), 1, 0); + + // Enable receive irq + armcm_enable_irq(UART0_IRQHandler, UART0_IRQn, 0); + LPC_UART0->IER = 0x01; +}