├── README.md ├── _ui_test.cfg ├── _user_interaction.cfg └── pics └── MacroTitles&Comments.png /README.md: -------------------------------------------------------------------------------- 1 | # Klipper_UserInteraction 2 | A set of macros to enable print-time user interaction with Klipper via Console and UI buttons (macros). 3 | Copyright (C) 2022-2023 Tod A. Wulff 4 | 5 | This program is free software: you can redistribute it and/or modify 6 | it under the terms of the GNU General Public License as published by 7 | the Free Software Foundation, either version 3 of the License, or 8 | (at your option) any later version. 9 | 10 | This program is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | GNU General Public License for more details. 14 | 15 | Should you have a need to receive a copy of the GNU General Public License, 16 | see . 17 | 18 | 27Apr23 - Adopted GNU GPLv3 for these works. Added example of using UI to go interactive with user 19 | when host system cpu utilization exceeds some programmatic value at print start. 20 | 21 | 04APR23 Update: Added example of using UI Interaction during an Idle Timeout Event. 22 | 23 | 19MAR23 Update: Added Text Decoration to the queries. Updated code herein with the latest from my 24 | production printer environment. Added verbal hinting/reminding with TTS macro calls: say & say_wait. 25 | 26 | Macro list/comments: 27 | ![https://i.imgur.com/AvHwSYA.png](https://i.imgur.com/AvHwSYA.png) 28 | 29 | Video of an early implementation: https://youtu.be/pgBfhVAYsHU 30 | 31 | Note that the files herein may not be the latest - 'production' code is attainable via my printer config: 32 | https://github.com/TodWulff/V2.2526_Config/blob/main/_user_interaction.cfg and 33 | https://github.com/TodWulff/V2.2526_Config/blob/main/_ui_test.cfg 34 | 35 | Here is an example of a real world use case - optionally keeping heaters on & unloading filament @ print end: 36 | ![https://i.imgur.com/jfVYFZx.png](https://i.imgur.com/jfVYFZx.png) 37 | 38 | Here is another example of a real world use case - optionally rebooting after an ERCF calibration event: 39 | ![https://i.imgur.com/PvLhUWd.png](https://i.imgur.com/PvLhUWd.png) 40 | ![https://i.imgur.com/Tn1ANaH.png](https://i.imgur.com/Tn1ANaH.png) 41 | 42 | Other cases in which I employ this module in my daily printing workflow: 43 | 44 | - During an Idle Timeout event, give user ability to cancel the shutdown, to proceed, and to upload configs and then proceed, 45 | with null-input timeout to default to proceed with the shutdown (as imaged). 46 | 47 | ![https://i.imgur.com/mZ7V0rl.png](https://i.imgur.com/mZ7V0rl.png) 48 | 49 | - At print start, if system cpu utilization exceeds a threshold, go interactive to give user an opportunity to resolve the issue (wait for the host to settle down (i.e. if transcoding a timelapse from a prior print), or to change the threshold value via the console). 50 | 51 | ![https://i.imgur.com/RYv5SMl.png](https://i.imgur.com/RYv5SMl.png) 52 | 53 | - At print start, if extrusion factor or speed factor is not 100%, go interactive to give user option to reset each to 100% before commencing the print: 54 | 55 | ![https://i.imgur.com/v5AvWXp.png](https://i.imgur.com/v5AvWXp.png) 56 | 57 | - At print canx/end, give user opportunity to retain heater settings, or to turn heaters off; 58 | - At print canx/end, give user option to retain or unload filament (12-color Multi-Material ERCF here); 59 | - At print end, give user opportunity to push current configs up to github repo, for historization and disaster recovery purposes: 60 | - The '_git_repo_ops' macro set has been enhanced to query the user for a 1-72 character commit summary message 61 | 62 | ![https://i.imgur.com/5y42HaG.png](https://i.imgur.com/5y42HaG.png) 63 | 64 | - Using UI to enable SomaFM Web Streamed Radio channel selection by displaying an index of channel names/numbers and querying the user to make a selection: 65 | 66 | ![https://i.imgur.com/IKu9OLo.png](https://i.imgur.com/IKu9OLo.png) 67 | 68 | - During calibration of ERCF Filament Encoder, for each color/cart, give user ability to restart the calibration or to accept same and continue 69 | - Used UI module during testing of various hardware items (i.e. servo throw angle determinations for v0.1 nozzle scrubber and side swipe klicky/euclid bed probes) 70 | - ... more that I cannot recall right now 71 | 72 | 73 | ## USER-SUBMITTED USE CASES: 74 | 75 | Here is a user-submitted example of using this macro library to implement an interactive M600 Filament Change process: 76 | https://gist.github.com/Beatzoid/b66cad5ed74f2ad23529857bcc45636c 77 | Thanks to Discord User Beatzoid#8010 for providing this fine example. 78 | 79 | 80 | ## PREREQUISITES: 81 | 82 | Heavily relies on `[Save_Variables]` module 83 | see: https://github.com/TodWulff/V2.2526_Config/blob/main/_persistent_variables.cfg 84 | 85 | Makes use of **`M300`** and some custom **`M300_`** related macros I mucked with for emitting sounds, so if you're not so 86 | interested, or haven't a beeper on your box, comment out related lines: `_ui_reminder`, `_annunciate_input_exception`, 87 | and `_annunciate_good_input` - see: https://github.com/TodWulff/V2.2526_Config/blob/main/_m300_sounds.cfg 88 | 89 | Makes heavy use of the **`[response]`** module - I've trapped the stock M118 (using rename_existing) with gcode such 90 | that it uses action_notification vs. FW M118 code. This enables emission of 'special characters' that M118's 91 | FW code chokes on - see: https://github.com/TodWulff/V2.2526_Config/blob/main/_gcode_macros.cfg 92 | 93 | Throughout my configs, virtually all gcode macros have been 'instrumented' - the first and last lines of each gcode block can be deleted. 94 | I have these as I have an ability to trace macro code execution and display same in the console - quite powerful for macro development/ 95 | troubleshooting but of little/no use to others, with rare exception - 96 | see: https://github.com/TodWulff/V2.2526_Config/blob/main/_debug_trace.cfg 97 | 98 | Users should consider adding a User Input macro pane, as depicted below - which calls macros herein. 99 | Orienting it under the console pane will allow it to become intuitive with a small bit of use. Author's UI of choice is Mainsail. 100 | For Fluidd or other Moonraker Clients (Mooncord/Telegram Bot/...), I defer to others to adapt the macros for use therein. 101 | 102 | ![https://i.imgur.com/QVxLuVZ.png](https://i.imgur.com/QVxLuVZ.png) 103 | 104 | ## Primary Macro: GET_USER_INPUT Parameters and related dialog follows 105 | 106 | Optional Parameters: 107 | 108 | **`PROMPT`** Quoted Text to display in console as a user input prompt. Promts are vistually separated from balance 109 | of console context via a dashed line being displayed before and after the prompt (see images below). 110 | 111 | **`RCVR_MACRO`** Macro name to run when VALID input received - UI_INPUT param, containing user input contents is 112 | passed to the called macro - if required, the called user macro can query svv for additional details. 113 | 114 | **`TYPE`** The TYPE of input needed - one of these three string/str, integer/int, float/flt 115 | 116 | **`BOUNDS_HI`** if TYPE is float/flt, input must be >=lo and <=hi - for integer/int, input must be >=floor(lo) and <=ceiling(hi) 117 | 118 | **`BOUNDS_LO`** - if string/str TYPE, character count must be >=floor(lo) and <=ceiling(hi) 119 | 120 | **`TO_PERIOD`** Period in Integer seconds to wait for user input 121 | 122 | **`RMDR_PERIOD`** Overrides the default reminder period set in \_ui_vars while waiting for user input 123 | 124 | **`EXCPT_HDLR`** Macro name is called in the event of an input timeout or faulty input - no params passed - query svv... 125 | 126 | **`TO_CYCL_DEF`** iterations of timeouts before TO_RESP_DEF is sent as UI_INPUT to RCVR_MACRO - default: -1 to disable behavior 127 | 128 | **`TO_RESP_DEF`** UI_INPUT value to be passed if TO_CYCL_DEF count reaches 0 when decremented @ timeout 129 | 130 | For string TYPE, if **`BOUNDS_LO`** is not asserted, defaults to 1. 131 | 132 | For string TYPE, if **`BOUNDS_HI`** is not asserted, defaults to 255. 133 | 134 | For float TYPE, if **`BOUNDS_LO`** is not asserted, defaults to -999999999.0. 135 | 136 | For float TYPE, if **`BOUNDS_HI`** is not asserted, defaults to 999999999.0. 137 | 138 | For integer TYPE, if **`BOUNDS_LO`** is not asserted, defaults to -999999999. 139 | 140 | For integer TYPE, if **`BOUNDS_HI`** is not asserted, defaults to 999999999 141 | 142 | If additional bounds testing is desired/required, it's up to the user implementing this on their printers to craft more granular 143 | test and validation - by way of the **`RCVR_MACRO`**. If I've blantantly missed something, then let me know. :) 144 | 145 | For optional parameters, read the code to understand implications of relying on defaults. 146 | 147 | Again, ALL parameters are optional and default to something (depicted in the following): 148 | 149 | PROMPT="Awaiting User Input:" displayed on the console at macro start as a user prompt 150 | TYPE=STRING 'string'('str') or 'integer'('int') or 'float'('flt') - for buttons use string 151 | BOUNDS_LO=1 min string chars or min numercial value (Int/Flt -999999999) 152 | BOUNDS_HI=255 max string chars or max numercial value (Int/Flt 999999999) 153 | RCVR_MACRO="_test_show_user_input" to accept param UI_INPUT that will be an int/flt/"string" that was 154 | input and passes sniff test (simple bounding checks) 155 | TO_PERIOD=120 in seconds 156 | EXCPT_HDLR="_ui_exception_handler" no params passed to this proc - use svv to get runtime specifics if needed 157 | TO_CYCL_DEF=-1 158 | TO_RESP_DEF="NULL" if TO_CYCLES >=0, this param will be passed to RCVR_MACRO via UI_INPUT if no user input received 159 | RMDR_PERIOD=15 reminder bleeps every n seconds - 0 will disable reminder beeps 160 | 161 | Also, there are four other module macro variables that affect the function of the macros 162 | 163 | variable_ui_input_check_recurse_period: 0.5 # seconds - period between checking for input when expected - 0.5s is good - less leads to more host 164 | # cycle consumption, larger leads to reduced perceived responsiveness 165 | variable_ui_reminder_enable: 1 # bool 1/0 - 0 overtly disables reminder bleeps regardless of RMDR_PERIOD param 166 | variable_ui_enable_input_hints: 0 # bool 1/0 - defaults to disabling hints on input prompt 167 | variable_ui_disable_exception_hints: 0 # bool 1/0 - defaults to enabling hints on an exception event 168 | 169 | These can be programmatically altered during runtime via use of SET_GCODE_VARIABLE gcode command - i.e.: 170 | 171 | SET_GCODE_VARIABLE MACRO=_ui_vars VARIABLE=ui_enable_input_hints VALUE=1 172 | 173 | ### Example call follows: 174 | This call looks for a user to enter a string 1-12 chars long, with a timeout of 60 secs, that forwards (via UI_INPUT param), 175 | the entered string to the '_test_show_user_input' macro (default if no RCVR_MACRO passed by user call) 176 | If a timeout happens/faulty input is detected, the _ui_timeout_watchdog/_validate_user_input macros call '_ui_exception_handler' macro 177 | (which is the default exception handler if no EXCPT_HDLR macro name is passed by the user call) 178 | 179 | `get_user_input` `PROMPT="Enter/click something:"` `TYPE=string` `BOUNDS_LO=1` `BOUNDS_HI=12` `RCVR_MACRO=_test_show_user_input` `TO_PERIOD=60` `EXCPT_HDLR=_ui_exception_handler` 180 | 181 | ## EXCEPTION HANDLER MACRO: 182 | if a custom **`EXCPT_HDLR`** macro is to be instantiated, it may prove useful to consider the following: 183 | - **`GET_USER_INPUT`** does the following: 184 | - initializes states and then displays the user **`PROMPT`** 185 | - sets the timeout watchdog to fire after the asserted (120s default) **`TO_PERIOD`** 186 | - puts **`_await_user_input`** into a recursive loop, non-blocking while waiting, with a period defined in _ui_vars 187 | - a single **`EXCPT_HDLR`** macro services both bad input cases as well as the time-out when waiting on user input. 188 | - for timeouts the **default** **`EXCPT_HDLR`** macro simply recalls **`GET_USER_INPUT`** giving user another input context 189 | - this approach can be altered with a custom **`EXCPT_HDLR`** macro being passed to the **`GET_USER_INPUT`** call 190 | - When `_await_user_input` detects input from user, `_validate_user_input` tests for **`TYPE`** and **`BOUNDS_HI`**/**`BOUNDS_LO`** compliance 191 | - if input is NOT **`TYPE`** & **`BOUNDS_HI`**/**`BOUNDS_LO`** compliant, `_await_user_input` recalls **`GET_USER_INPUT`** so user can fix it 192 | - if input IS **`TYPE`** & **`BOUNDS_HI`**/**`BOUNDS_LO`** compliant (flag `_ui_bad_input` NOT set), input is sent to **`RCVR_MACRO`** via **`UI_INPUT`** param 193 | 194 | In most conceivable use cases, the default exception handler/validation macros should be adequate. However, author decided to give others 195 | options, in the event something wasn't adequately considered. Either an entirely new **`EXCPT_HDLR`** can be crafted or, as demonstrated 196 | in _ui_test.cfg's `_ui_test_exception_handler` (custom **`EXCPT_HDLR`**) code runs and then chains to the stock `_ui_exception_handler` below 197 | 198 | In `_ui_vars` macro, a person implementing this can selectively enable/disable Input Prompt and/or Exception hints. Hints are 199 | little descriptive blurbs as to what the macro is expecting as input - one can enable hints on either the input prompt, 200 | or disable hints when an input exception is detected/raised. It is suggested that it is likely best to have exception 201 | hints enabled, and to have the input prompt detail what sort of input is desired, leaving input hints disabled. 202 | The `_ui_test.cfg` file has the START_DEMO macro that iterates through some UI input event - I used it for dev testing, 203 | it works as a demo. 204 | 205 | ## Some visual examples as it relates to the hint options that can be set in `_ui_vars`: 206 | 207 | No Hints on Exception nor Input Prompt: https://i.imgur.com/PDPOmTJ.png (likely too terse - user should make the preamble/prompt detailed) 208 | 209 | ![https://i.imgur.com/PDPOmTJ.png](https://i.imgur.com/PDPOmTJ.png) 210 | 211 | Hints on Exception only, not on Input Prompt: https://i.imgur.com/D5Ih6hE.png (the author's preference) 212 | 213 | ![https://i.imgur.com/D5Ih6hE.png](https://i.imgur.com/D5Ih6hE.png) 214 | 215 | Hints on Input Prompt but not on an Exception: https://i.imgur.com/Deo2SNr.png (leads to some noise, which may be tolerable) 216 | 217 | ![https://i.imgur.com/Deo2SNr.png](https://i.imgur.com/Deo2SNr.png) 218 | 219 | Hints on both Input Prompt and when an exception is raised: https://i.imgur.com/mO7TfWW.png (likely redundant, imo) 220 | 221 | ![https://i.imgur.com/mO7TfWW.png](https://i.imgur.com/mO7TfWW.png) 222 | 223 | ## Closing comments: 224 | This was/is a quite deep rabbit hole. Author `MegaHurtz 🇺🇸#6544` can be reached on a number of different Discord servers: 225 | - DIY 3D Printer Kit Feedback 226 | - Voron Design 227 | - Klipper 228 | - Mainsail 229 | - ... 230 | 231 | Don't hesitate to reach out as may be needed. Have a great day. Happy Printing! 232 | 233 | ~MHz 234 | -------------------------------------------------------------------------------- /_ui_test.cfg: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------- 2 | # _user_interaction (UI) module: test macros 3 | # Copyright (C) 2022-2023 Tod A. Wulff 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Should you have a need to receive a copy of the GNU General Public License, 16 | # see . 17 | #-------------------------------------------------------------------- 18 | 19 | [delayed_gcode _ui_test_loaded] 20 | initial_duration: 4.501 21 | gcode: 22 | _proc_start function=_ui_test_loaded func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 23 | _module_loaded MODULE=_ui_test 24 | _proc_end function=_ui_test_loaded 25 | 26 | [delayed_gcode _ui_test_module_start] 27 | #description: Initialization 28 | initial_duration: 0.75 # have this at 0.75s as init code in _startup_autoexec.cfg runs at 0.1s after start 29 | gcode: 30 | 31 | _proc_start function=_ui_test_module_start func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 32 | 33 | _ui_clear_test_cache 34 | 35 | _proc_end function=_ui_test_module_start 36 | 37 | #-------------------------------------------------------------------- 38 | 39 | [gcode_macro _ui_clear_test_cache] 40 | description: helper proc to wipe/initialize svv contents related to user input 41 | gcode: 42 | 43 | _proc_start function=_ui_clear_test_cache func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 44 | 45 | SAVE_VARIABLE VARIABLE=ui_test_day_name VALUE='""' 46 | SAVE_VARIABLE VARIABLE=ui_test_day_num VALUE=0 47 | SAVE_VARIABLE VARIABLE=ui_test_float VALUE=-9999999.99 48 | SAVE_VARIABLE VARIABLE=ui_test_mo_name VALUE='""' 49 | SAVE_VARIABLE VARIABLE=ui_test_mo_num VALUE=-9999999 50 | SAVE_VARIABLE VARIABLE=ui_test_year VALUE=-9999999 51 | 52 | _proc_end function=_ui_clear_test_cache 53 | 54 | #-------------------------------------------------------------------- 55 | 56 | [gcode_macro _ui_test_exception_handler] 57 | description: demo use of an exception handler 58 | gcode: 59 | 60 | _proc_start function=_ui_test_exception_handler func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 61 | 62 | M118 Welp, you weren't quick enough or made an entry error 63 | M118 note the hints provide not only what is being asked for 64 | M118 but also how long the macro is parameterized to wait for user input... 65 | M118 This is a customized input exception handler for timeout and errorneous input. 66 | M118 The default handler is what you will observe now. 67 | M118 ------------------------------- 68 | 69 | _ui_exception_handler 70 | 71 | _proc_end function=_ui_test_exception_handler 72 | 73 | #-------------------------------------------------------------------- 74 | 75 | [gcode_macro _start_ui_test] 76 | description: for demo'g/testing the user interaction 'module' 77 | gcode: 78 | 79 | _proc_start function=_start_ui_test func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 80 | 81 | _ui_clear_test_cache 82 | 83 | M118 Good Day, Mate! The User Interaction (UI) demo/test is starting: 84 | M118 The User Interaction ('UI' going forward - not to be confused with User Interface) has 85 | M118 input type and value bounding features, timeout detection, and dynamic forwarding of 86 | M118 validated input to 'Receiver Macros' enabling interactivity and helping to ensure that 87 | M118 TYPE of input matches what was requested and is needed by the receiver macro. 88 | 89 | # note that when no bounds are asserted in the call, string lengths are bound from 1 to 255 characters as hinted to 90 | get_user_input prompt="Press any of the UI buttons below to continue:" TYPE=str RCVR_MACRO=_starta_ui_test EXCPT_HDLR=_ui_test_exception_handler 91 | 92 | _proc_end function=_start_ui_test 93 | 94 | #-------------------------------------------------------------------- 95 | 96 | [gcode_macro _starta_ui_test] 97 | description: for demo'g/testing the user interaction 'module' 98 | gcode: 99 | 100 | _proc_start function=_starta_ui_test func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 101 | 102 | M118 There ya go - You clicked/entered: {params.UI_INPUT} 103 | 104 | M118 This series of test macros serve to demonstrate these features. 105 | M118 Timeouts can be assigned from 0 to ~infinity seconds (defaults to 120 seconds). 106 | M118 This particular test step has an intentionally short timeout assigned (15s). 107 | M118 This is so user can observe a timeout without having to wait for 2 minutes... 108 | 109 | get_user_input prompt="Press any of the UI buttons below to continue:" TYPE=str RCVR_MACRO=_startb_ui_test EXCPT_HDLR=_ui_test_exception_handler TO_PERIOD=15 110 | 111 | _proc_end function=_starta_ui_test 112 | 113 | #-------------------------------------------------------------------- 114 | 115 | [gcode_macro _startb_ui_test] 116 | description: for demo'g/testing the user interaction 'module' 117 | gcode: 118 | 119 | _proc_start function=_startb_ui_test func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 120 | 121 | M118 Oh Yeah.!. You clicked/entered: {params.UI_INPUT} 122 | 123 | M118 For an aid in understanding things a bit better, it might be useful to 'tail' the saved vars file 124 | M118 so that you can see what data is stored and changes as user interaction takes place. You can do 125 | M118 with a command such as: tail -f -n 30 /home/pi/printer_data/config/your_saved_vars_file.cfg 126 | M118 in a ssh terminal window. 127 | 128 | get_user_input prompt="Press any of the UI buttons below to continue:" TYPE=str RCVR_MACRO=_startc_ui_test EXCPT_HDLR=_ui_test_exception_handler 129 | 130 | _proc_end function=_startb_ui_test 131 | 132 | #-------------------------------------------------------------------- 133 | 134 | [gcode_macro _startc_ui_test] 135 | description: for demo'g/testing the user interaction 'module' 136 | gcode: 137 | 138 | _proc_start function=_startc_ui_test func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 139 | 140 | M118 Schuite. You clicked/entered: {params.UI_INPUT} 141 | 142 | M118 The UI module provides hints on the input needed, how quickly it needs to be responded to, 143 | M118 and also provides a gentle reminder nudge every 30 seconds (customizable period) while waiting 144 | M118 for user input. 145 | 146 | get_user_input prompt="Press any of the UI buttons below to continue:" TYPE=str RCVR_MACRO=_startd_ui_test EXCPT_HDLR=_ui_test_exception_handler 147 | 148 | _proc_end function=_startc_ui_test 149 | 150 | #-------------------------------------------------------------------- 151 | 152 | [gcode_macro _startd_ui_test] 153 | description: for demo'g/testing the user interaction 'module' 154 | gcode: 155 | 156 | _proc_start function=_startd_ui_test func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 157 | 158 | M118 Kudos. You clicked/entered: {params.UI_INPUT} 159 | 160 | M118 For testing, you can let the input timeout and observe the reminder firing and also what happens 161 | M118 on a timeout, and use the USER ENTRY button to enter an integer/float vs. hitting one of the buttons 162 | M118 to see what the response is to an invalid input TYPE. 163 | M118 On this first interaction, the user is requested to enter an integer year within 30 seconds. 164 | 165 | get_user_input prompt="Press any of the UI buttons below to continue:" TYPE=str RCVR_MACRO=_ui_test_get_year EXCPT_HDLR=_ui_test_exception_handler 166 | 167 | _proc_end function=_startd_ui_test 168 | 169 | #-------------------------------------------------------------------- 170 | 171 | [gcode_macro _ui_test_get_year] 172 | description: first test proc, to get integer year 173 | # note that the proc that is chained to gets passes the previous input in parameter UI_INPUT 174 | # further, the saved variables has additional information which is progammatically exposed 175 | gcode: 176 | 177 | _proc_start function=_ui_test_get_year func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 178 | 179 | M118 Very good. You clicked/entered: {params.UI_INPUT} 180 | M118 Next we're going to be asking for the year. It is programmatically bound to the years 1900 to 2500. 181 | M118 Enter an integer, using the USER ENTRY button, that falls outside this range to test integer bounds 182 | M118 checking and validation. You should note the hints provided and also the default timeout and/or 183 | M118 input exception script functionality. On this test step, other than RCVR_MACRO macro, TYPE, and bound 184 | M118 limits assignment, all other parameters of get_user_input are left to default. 185 | 186 | get_user_input prompt="Enter the year:" TYPE=int BOUNDS_LO=1900 BOUNDS_HI=2500 RCVR_MACRO=_ui_test_get_mo_name 187 | 188 | _proc_end function=_ui_test_get_year 189 | 190 | #-------------------------------------------------------------------- 191 | 192 | [gcode_macro _ui_test_get_mo_name] 193 | description: second test proc - chained here by parameters from the first test proc 194 | gcode: 195 | 196 | _proc_start function=_ui_test_get_mo_name func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 197 | 198 | SAVE_VARIABLE VARIABLE=ui_test_year VALUE='{params.UI_INPUT}' # save the value for later use 199 | 200 | M118 Outstanding. You entered Year: {params.UI_INPUT} 201 | M118 Next we're going to be asking for the month name in the form of a string. For strings, the bounding 202 | M118 parameters are used to enforce entered string length limits. In the case of month name, bounding limits 203 | M118 will be set to accept no less than 3 characters (May is the shortest named month) and no more than 204 | M118 9 characters (September is the longest named month). For TYPE enforcement validation, try to enter an 205 | M118 Integer or Float, or try to enter a string with fewer than 3 or more than 9 characters - 60sec timeout 206 | 207 | get_user_input prompt="Enter the name of the month:" TYPE=str BOUNDS_LO=3 BOUNDS_HI=9 RCVR_MACRO=_ui_test_get_mo_num TO_PERIOD=60 208 | 209 | _proc_end function=_ui_test_get_mo_name 210 | 211 | #-------------------------------------------------------------------- 212 | 213 | [gcode_macro _ui_test_get_mo_num] 214 | description: 215 | gcode: 216 | 217 | _proc_start function=_ui_test_get_mo_num func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 218 | 219 | SAVE_VARIABLE VARIABLE=ui_test_mo_name VALUE='{params.UI_INPUT}' # save the value for later use 220 | 221 | M118 Great. You entered month: {params.UI_INPUT} 222 | M118 Next we're going to be asking for the month number in the form of a bound integer (1-12). 223 | M118 You will have 45 seconds to do this. Be aware that too short of a TO_PERIOD will net drama for 224 | M118 the user. The default TO_PERIOD is 120 seconds - recommended caution with short TO_PERIODs, and 225 | M118 to not have the exception handler be destructive when a timeout occurs - life happens. 226 | 227 | get_user_input prompt="Enter the number of the month:" TYPE=int BOUNDS_LO=1 BOUNDS_HI=12 RCVR_MACRO=_ui_test_get_day_num TO_PERIOD=45 228 | 229 | _proc_end function=_ui_test_get_mo_num 230 | 231 | #-------------------------------------------------------------------- 232 | 233 | [gcode_macro _ui_test_get_day_num] 234 | description: 235 | gcode: 236 | 237 | _proc_start function=_ui_test_get_day_num func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 238 | 239 | SAVE_VARIABLE VARIABLE=ui_test_mo_num VALUE='{params.UI_INPUT}' # save the value for later use 240 | 241 | M118 Noice. You entered month number: {params.UI_INPUT} 242 | M118 Next we're going to be asking for the day in the month in the form of a bound integer (1-31). 243 | M118 You will have 45 seconds to input this value. 244 | 245 | get_user_input prompt="Enter the number of the day of the month:" TYPE=int BOUNDS_LO=1 BOUNDS_HI=31 RCVR_MACRO=_ui_test_get_day_name TO_PERIOD=45 246 | 247 | _proc_end function=_ui_test_get_day_num 248 | 249 | #-------------------------------------------------------------------- 250 | 251 | [gcode_macro _ui_test_get_day_name] 252 | description: 253 | gcode: 254 | 255 | _proc_start function=_ui_test_get_day_name func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 256 | 257 | SAVE_VARIABLE VARIABLE=ui_test_day_num VALUE='{params.UI_INPUT}' # save the value for later use 258 | 259 | M118 Good. You entered day number: {params.UI_INPUT} 260 | M118 Next we're going to be asking for the day name in the form of a string. Bounding limits set to 261 | M118 6 to 9 string characters. 262 | 263 | get_user_input prompt="Enter the name of the day today:" TYPE=str BOUNDS_LO=6 BOUNDS_HI=9 RCVR_MACRO=_ui_test_get_float 264 | 265 | _proc_end function=_ui_test_get_day_name 266 | 267 | #-------------------------------------------------------------------- 268 | 269 | [gcode_macro _ui_test_get_float] 270 | description: 271 | gcode: 272 | 273 | _proc_start function=_ui_test_get_float func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 274 | 275 | SAVE_VARIABLE VARIABLE=ui_test_day_name VALUE='{params.UI_INPUT}' # save the value for later use 276 | 277 | M118 Wicked kewl. You entered day name of: {params.UI_INPUT} 278 | M118 Next we're going to be asking for a float (decimal number). These are default bound to values ranging 279 | M118 from -999999999.0 to 999999999.0. 280 | 281 | get_user_input prompt="Enter a float:" TYPE=flt RCVR_MACRO=_ui_test_conclude 282 | 283 | _proc_end function=_ui_test_get_float 284 | 285 | #-------------------------------------------------------------------- 286 | 287 | [gcode_macro _ui_test_conclude] 288 | description: 289 | gcode: 290 | 291 | _proc_start function=_ui_test_conclude func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 292 | 293 | SAVE_VARIABLE VARIABLE=ui_test_float VALUE='{params.UI_INPUT}' # save the value for later use 294 | 295 | M118 Good deal. You entered the value: {params.UI_INPUT} 296 | M118 The author of this module may expand this test/demo demonstrating use/validation of button 297 | M118 presses, but hasn't yet identified a time frame for doing so. Stand by for same... :) 298 | 299 | get_user_input prompt="Press any of the UI buttons below to conclude this test/demo:" TYPE=str RCVR_MACRO=_ui_test_concluded 300 | 301 | _proc_end function=_ui_test_conclude 302 | 303 | #-------------------------------------------------------------------- 304 | 305 | [gcode_macro _ui_test_concluded] 306 | description: 307 | gcode: 308 | 309 | _proc_start function=_ui_test_concluded func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 310 | 311 | M118 AAAnnndddd, you clicked/entered: {params.UI_INPUT} 312 | 313 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 314 | 315 | {% set ui_test_interaction_results = 316 | "Year: " ~ svv.ui_test_year ~ "
" ~ 317 | "Month, Num: " ~ svv.ui_test_mo_name ~ ", " ~ svv.ui_test_mo_num ~ "
" ~ 318 | "Day, Num: " ~ svv.ui_test_day_name ~ ", " ~ svv.ui_test_day_num ~ "
" ~ 319 | "Entered Float: " ~ svv.ui_test_float ~ "

" 320 | %} 321 | 322 | M118 {"Codified and Saved Test Results:
" ~ ui_test_interaction_results} 323 | M118 This concludes the demo of the UI system. This was only a test. Had this been a real use case 324 | M118 things would have certianly been less verbose and more efficient, and your steppers might not have melted. :) 325 | M118 326 | M118 Have a great day. Happy Printing!!! -MHz 327 | 328 | _proc_end function=_ui_test_concluded 329 | 330 | #-------------------------------------------------------------------- 331 | 332 | [gcode_macro STOP_DEMO] # for UI button 333 | description: 334 | gcode: 335 | 336 | _proc_start function=STOP_DEMO func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 337 | 338 | _stop_ui_input_loop 339 | _stop_ui_reminder 340 | _stop_ui_timeout_watchdog 341 | _ui_clear_cache 342 | _ui_clear_test_cache 343 | 344 | _proc_end function=STOP_DEMO 345 | 346 | #-------------------------------------------------------------------- 347 | 348 | [gcode_macro START_DEMO] # for UI button 349 | description: 350 | gcode: 351 | 352 | _proc_start function=START_DEMO func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 353 | 354 | _start_ui_test 355 | 356 | _proc_end function=START_DEMO 357 | 358 | #-------------------------------------------------------------------- 359 | -------------------------------------------------------------------------------- /_user_interaction.cfg: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------- 2 | # _user_interaction (UI) gcode 'module' 3 | # Copyright (C) 2022-2023 Tod A. Wulff 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # Should you have a need to receive a copy of the GNU General Public License, 16 | # see . 17 | #-------------------------------------------------------------------- 18 | # https://github.com/TodWulff/Klipper_UserInteraction 19 | #-------------------------------------------------------------------- 20 | # Module Includes 21 | [include _ui_test.cfg] # includes testing macros and a set of interrelated UI demonstration macros 22 | 23 | #-------------------------------------------------------------------- 24 | 25 | [delayed_gcode _user_interaction_loaded] # template macro for user module loading advisement, when so enabled 26 | initial_duration: 4.501 27 | gcode: 28 | _proc_start function=_user_interaction_loaded func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 29 | 30 | _module_loaded MODULE=_user_interaction 31 | 32 | _proc_end function=_user_interaction_loaded 33 | 34 | #-------------------------------------------------------------------- 35 | 36 | [gcode_macro _info_ui_module] # template macro with module prerequisites/documentation/notes/examples/etc. 37 | 38 | gcode: 39 | # This macro _info_ui_module is for module documentation only - isn't intended to be called - no gcode herein is intended to be ran 40 | 41 | # This klipper gcode macro module heavily relies on [Save_Variables] module 42 | # see: https://github.com/TodWulff/V2.2526_Config/blob/main/_persistent_variables.cfg 43 | 44 | # This gcode module makes use of M300 and custom M300 related macros for emitting sounds, so if you're not so 45 | # interested, or haven't a beeper on your box, comment out related lines: _ui_reminder, _annunciate_input_exception, 46 | # and _annunciate_good_input - see: https://github.com/TodWulff/V2.2526_Config/blob/main/_m300_sounds.cfg 47 | 48 | # This gcode module makes heavy use of the [response] module - stock M118 trapped (using rename_existing) so 49 | # that it uses action_notification vs. FW M118 code. This enables emission of 'special characters' that M118's 50 | # FW code chokes on - see: https://github.com/TodWulff/V2.2526_Config/blob/main/_gcode_macros.cfg 51 | 52 | # Throughout my configs, virtually all gcode has been 'instrumented' - the first/last set if lines in each gcode 53 | # block can/should be deleted. I have these as I have an ability to trace macro code execution and display same 54 | # via console - powerful for macro development/troubleshooting but likely of little/no use to others 55 | # see: https://github.com/TodWulff/V2.2526_Config/blob/main/_debug_macros.cfg 56 | 57 | # Users CAN/should add a User Input macro pane, depicted: https://i.imgur.com/QVxLuVZ.png, which calls macros herein 58 | # Orienting it under the console pane will allow it to become intuitive with a small bit of use 59 | 60 | # Primary Macro: GET_USER_INPUT Parameters and related dialog follows 61 | 62 | # Optional Parameters: 63 | # PROMPT Text to display in console as a user input prompt 64 | # RCVR_MACRO Macro name to run when VALID input received - UI_INPUT param passed to proc - if rcv'd, query svv for moar info 65 | # TYPE The TYPE of input needed - one of these three string/str, integer/int, float/flt 66 | # BOUNDS_HI if TYPE is float/flt, input must be >=lo and <=hi - for integer/int, input must be >=floor(lo) and <=ceiling(hi) 67 | # BOUNDS_LO - if string/str TYPE, character count must be >=floor(lo) and <=ceiling(hi) 68 | # TO_PERIOD Period in Integer seconds to wait for user input - a reminder M300 fires at rate in _ui_vars while waiting 69 | # EXCPT_HDLR Macro name is called in the event of an input timeout or faulty input - no params passed - query svv... 70 | # TO_CYCL_DEF Defaults to -1 (disabled) >=0 integer TO_period expiration cycles to occur before TO_DEF_RESP is sent to RCVR_MACRO 71 | # TO_RESP_DEF if TO_CYCLES >=0, this param will be passed to RCVR_MACRO via UI_INPUT if no user input received 72 | # RMDR_PERIOD overrides reminder period in _ui_vars 73 | 74 | # For string TYPE, if BOUNDS_LO is not asserted, defaults to 1 75 | # For string TYPE, if BOUNDS_HI is not asserted, defaults to 255 76 | 77 | # For float TYPE, if BOUNDS_LO is not asserted, defaults to -999999999.0 78 | # For float TYPE, if BOUNDS_HI is not asserted, defaults to 999999999.0 79 | 80 | # For integer TYPE, if BOUNDS_LO is not asserted, defaults to -999999999 81 | # For integer TYPE, if BOUNDS_HI is not asserted, defaults to 999999999 82 | 83 | # If additional bounds testing is desired/required, it's up to the user implementing this on their printers to craft more granular 84 | # test and validation - by way of the RCVR_MACRO. If I've blatantly missed something, then let me know. :) 85 | 86 | # For optional parameters, read the code to understand implications of relying on defaults. 87 | 88 | # Again, ALL options are optional and default to something - defaults are as entered or noted 89 | # get_user_input PROMPT="Enter or Click something:" # displayed on the console at macro start as a user prompt 90 | # TYPE=STRING # 'string' or 'integer' or 'float' - for buttons use string 91 | # BOUNDS_LO=1 (Int/Flt -999999999) # min string chars or min numerical value (int/flt) 92 | # BOUNDS_HI=255 (Int/Flt 999999999) # max string chars or max numerical value (int/flt) 93 | # RCVR_MACRO=_test_show_user_input # to accept param UI_INPUT that will be an int/flt/"string" that was 94 | # # input and passes sniff (simple bounds) test 95 | # TO_PERIOD=120 # in seconds 96 | # EXCPT_HDLR=_ui_exception_handler # no params passed - use svv to get runtime specifics if needed 97 | # TO_CYCL_DEF - Defaults to -1 (disabled) >=0 integer TO_period expiration cycles to occur before TO_DEF_RESP is sent to RCVR_MACRO 98 | # TO_RESP_DEF - if TO_CYCLES >=0, this param will be passed to RCVR_MACRO via UI_INPUT if no user input received 99 | # RMDR_PERIOD # overrides reminder period in _ui_vars 100 | # 101 | # Example call follows: 102 | # This looks for a user to enter a string 1-12 chars long, with a timeout of 30 secs, that forwards (via UI_INPUT param), 103 | # the entered string to the '_test_show_user_input' macro (default if no RCVR_MACRO passed by user call) 104 | # If a timeout happens/faulty input is detected, the _ui_timeout_watchdog/_validate_user_input macros call '_ui_exception_handler' macro 105 | # (which is the default exception handler if no EXCPT_HDLR macro name is passed by the user call) 106 | # being reminded every 10 seconds and after 1 requery and subsequent timeouts, "YES" will be sent as a default ui input response. 107 | get_user_input PROMPT="Enter or Click something:" TYPE=string BOUNDS_LO=1 BOUNDS_HI=12 RCVR_MACRO=_test_show_user_input TO_PERIOD=30 RMDR_PERIOD=10 EXCPT_HDLR=_ui_exception_handler TO_CYCL_DEF=1 TO_RESP_DEF="YES" 108 | 109 | # EXCEPTION HANDLER MACRO: 110 | # if a custom EXCPT_HDLR macro is to be instantiated, it may prove useful to consider the following: 111 | # - GET_USER_INPUT does the following: 112 | # a. initializes states and then displays the user PROMPT 113 | # b. sets the timeout watchdog to fire after the passed/default TO_PERIOD 114 | # c. puts _await_user_input into a recursive loop 115 | # - a single EXCPT_HDLR macro services both bad input cases as well as the time-out when waiting on user input. 116 | # - for timeouts the default EXCPT_HDLR macro simply recalls GET_USER_INPUT giving user another input context 117 | # - this approach can be altered with a custom EXCPT_HDLR macro being passed to the GET_USER_INPUT call 118 | # - When _await_user_input senses user input, _validate_user_input tests for TYPE and BOUNDS_HI/BOUNDS_LO compliance 119 | # - if input is NOT TYPE & BOUNDS_HI/BOUNDS_LO compliant, _await_user_input recalls GET_USER_INPUT so user can fix it 120 | # - if input IS TYPE & BOUNDS_HI/BOUNDS_LO compliant (_ui_bad_input NOT set), input is sent to RCVR_MACRO via UI_INPUT param 121 | 122 | # If a custom EXCPT_HDLR macro is employed, when/if the get_user_input {svv.ui_input_macro_rawparams} is called to requery, 123 | # be sure to include the helper param REQUERY=1, else the countdown to default response won't work - i.e.: 124 | # get_user_input {svv.ui_input_macro_rawparams} REQUERY=1 125 | # other use of this param is done at your own risk... :) 126 | 127 | # in most conceivable use cases, the default exception handler/validation macros should be adequate, but want to give others 128 | # options, in the event I haven't considered something. Either an entirely new EXCPT_HDLR can be crafted or, as demonstrated 129 | # in _ui_test.cfg's _ui_test_exception_handler (custom EXCPT_HDLR) code runs and then calls the stock _ui_exception_handler below 130 | 131 | # When sending UI_INPUT to RCVR_MACRO and a string is the asserted response TYPE, when performing conditional 132 | # tests in the RCVR_MACRO, they should have the form of (note the '" pairs that surround the contents): 133 | # https://i.imgur.com/2FOqUzQ.png 134 | 135 | # In _ui_vars, a person implementing this can selectively enable/disable Input Prompt and/or Exception hints. Hints are 136 | # little descriptive blurbs as to what the code is expecting as input - one can enable hints on either the input prompt, 137 | # or disable hints when an input exception is detected/raised. It is suggested that it is likely best to have exception 138 | # hints enabled, and to have the input prompt detail what sort of input is desired. The _ui_test.cfg file has the START_DEMO 139 | # macro that iterates through some UI input event - I used it for dev testing, it works as a demo. 140 | # Some visual examples as it relates to the hint options that can be set in _ui_vars: 141 | 142 | # No Hints on Exception nor Input Prompt: https://i.imgur.com/PDPOmTJ.png (likely too? terse - make prompt/preamble detailed) 143 | # Hints on Exception only, not on Input Prompt: https://i.imgur.com/D5Ih6hE.png (the author's preference) 144 | # Hints on Input Prompt but not on an Exception: https://i.imgur.com/Deo2SNr.png (leads to some noise, which may be tolerable) 145 | # Hints on both Input Prompt and when an exception is raised: https://i.imgur.com/mO7TfWW.png (likely too chatty & redundant, imo) 146 | 147 | #-------------------------------------------------------------------- 148 | 149 | [gcode_macro _ui_vars] # template macro for user module - base config default variables for the _User_Interaction module 150 | description: ui module variables 151 | 152 | # Defaults to be used if param not passed when GET_USER_INPUT called for: 153 | # PROMPT, TYPE, BOUNDS_LO, BOUNDS_HI, RCVR_MACRO, TO_PERIOD, EXCPT_HDLR, TO_CYCL_DEF, TO_RESP_DEF, RMDR_PERIOD 154 | 155 | variable_ui_input_def_param_prompt: "Awaiting User Input:" # Text to display in console as a user input prompt 156 | variable_ui_input_def_param_type: "STRING" # The TYPE of input needed - one of these three string/str, integer/int, float/flt 157 | variable_ui_input_def_param_bounds_lo_str: 1 # - if string/str TYPE, character count must be >=floor(lo) and <=ceiling(hi) 158 | variable_ui_input_def_param_bounds_hi_str: 255 # if TYPE is float/flt, input must be >=lo and <=hi - for integer/int, input must be >=floor(lo) and <=ceiling(hi) 159 | variable_ui_input_def_param_bounds_lo_num: -999999999 # integer or float input default max value accepted 160 | variable_ui_input_def_param_bounds_hi_num: 999999999 # integer or float input default min value accepted 161 | variable_ui_input_def_param_rcvr_macro: "_test_show_user_input" # Macro name to run when VALID input received - UI_INPUT param passed to proc - if reqd, query svv for moar info 162 | variable_ui_input_def_param_to_period: 120 # Period in Integer seconds to wait for user input - a reminder M300 fires at rate in _ui_vars while waiting 163 | variable_ui_input_def_param_excpt_hdlr: "_ui_exception_handler" # Macro name is called in the event of an input timeout or faulty input - no params passed - query svv... 164 | variable_ui_input_def_param_to_cycl_def: -1 # Defaults to -1 (disabled) >=0 integer TO_period expiration cycles to occur before TO_DEF_RESP is sent to RCVR_MACRO 165 | variable_ui_input_def_param_to_resp_def: "NULL" # if TO_CYCLES >=0, this param will be passed to RCVR_MACRO via UI_INPUT if no user input received 166 | variable_ui_input_def_param_rmdr_period: 15 # reminder bleeps every n seconds - 0 will disable reminder beeps 167 | 168 | # input scan loop iteration period (salient only when awaiting user input) 169 | variable_ui_input_check_recurse_period: 0.5 #seconds - period between checking for input when expected - 0.5s is good. 170 | # more delay (slower testing iteration) and UX presents as less responsive 171 | # less delay and host/klipper may get needlessly tasked (admitted speculation) 172 | # aural reminder related vars 173 | variable_ui_reminder_enable: 1 #bool 1/0 - 0 overtly disables reminder bleeps regardless of RMDR_PERIOD param 174 | 175 | # expected input hinting on user input promps and exceptions 176 | variable_ui_enable_input_hints: 0 #bool 1/0 - 1 enabled module-generated input hints - 0 disables same in favor of 'macro hinting/prompts' 177 | variable_ui_disable_exception_hints: 0 #bool 1/0 - 1 disables exception hint emissions - prolly want this == 0... 178 | variable_ui_input_readback_flag: 0 #bool 1/0 - 1 enabled readback of user input value - overly chatty, but helpful for peeps with accessibility issues, maybe 179 | variable_ui_button_readback_flag: 0 #bool 1/0 - 1 enabled readback of user UI button click - overly chatty, but helpful for peeps with accessibility issues, maybe 180 | 181 | # in this case, used to squelch chatty input status polling in trace logs 182 | variable_debug_trace_emissions: 0 # 0=quiet 1=chatty (controls logging of _await_user_input proc) 183 | 184 | 185 | #ToDo - not yet implemented: 186 | #variable_ui_force_pause_on_query: 1 #bool 1/0 187 | 188 | # did you hear my eyes cage when they pitched through zenith on their way to nadir??? 189 | #variable_ui_he_timeout_on_input_timeout: 0 #bool 1/0 190 | #variable_ui_hotend_timeout_period: 0 #period in secs 191 | #variable_ui_bed_timeout_on_input_timeout: 0 #bool 1/0 192 | #variable_ui_bed_timeout_period: 0 #period in secs 193 | 194 | # this is a goal implementation, as it will ease module use 195 | #variable_ui_unified_input_field: 0 #bool 1/0 196 | 197 | #dunno if this is of value or not. will use for a bit before deciding to embrace or forego this 198 | #variable_ui_bypass_type_enforcement: 0 #bool 1/0 199 | #variable_ui_bypass_bounds_enforcement: 0 #bool 1/0 200 | 201 | gcode: 202 | 203 | _proc_start function=_ui_vars func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 204 | 205 | # there is none - for 'gcode module' variable use only 206 | 207 | _proc_end function=_ui_vars 208 | 209 | #-------------------------------------------------------------------- 210 | 211 | [delayed_gcode _ui_module_start] # template macro for user module - for module initialization purposes 212 | #description: Sets module-specific state flags for conditional use elsewhere. 213 | initial_duration: 0.5 # have this at 0.5s as init code in _startup_autoexec.cfg runs at 0.1s after start 214 | gcode: 215 | 216 | _proc_start function=_ui_module_start func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 217 | 218 | _ui_clear_cache 219 | 220 | SAVE_VARIABLE VARIABLE=ui_input_to_cycl_def VALUE=-1 #internal decremented counter to force a default response in event of n timeouts. 221 | SAVE_VARIABLE VARIABLE=module_ui_loaded VALUE=1 #flag via persistent variable that this module is loaded 222 | SAVE_VARIABLE VARIABLE=ui_err_flag VALUE=0 #init error flag via persistent variable that this module is not in error 223 | 224 | _proc_end function=_ui_module_start 225 | 226 | #-------------------------------------------------------------------- 227 | 228 | [delayed_gcode _ui_reminder] # provides repeating aural and verbal indications that user input is being sought 229 | # provides a repeating alarm to alert on a requirement for user input 230 | initial_duration: 0 231 | gcode: 232 | 233 | _proc_start function=_ui_reminder func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 234 | 235 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 236 | 237 | #do things here like flash leds, make noise, etc. for user attention purposes 238 | M300 P100 S3000 239 | say_wait S="Awaiting Input" 240 | # get cuter here and read back the prompt - need to scrub parenthized stuff, including parens... 241 | # get cute here with monitoring the count down to default response and progressively annoy the user... 242 | 243 | UPDATE_DELAYED_GCODE ID=_ui_reminder DURATION={svv.ui_input_rmdr_period} 244 | 245 | _proc_end function=_ui_reminder 246 | 247 | #-------------------------------------------------------------------- 248 | 249 | [gcode_macro _start_ui_reminder] # starts the repetative loop of reminding the user that input is being sought 250 | gcode: 251 | 252 | _proc_start function=_start_ui_reminder func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 253 | 254 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 255 | 256 | UPDATE_DELAYED_GCODE ID=_ui_reminder DURATION={svv.ui_input_rmdr_period} 257 | 258 | _proc_end function=_start_ui_reminder 259 | 260 | #-------------------------------------------------------------------- 261 | 262 | [gcode_macro _stop_ui_reminder] # stops the repetative reminder at receipt of some input 263 | gcode: 264 | 265 | _proc_start function=_stop_ui_reminder func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 266 | 267 | UPDATE_DELAYED_GCODE ID=_ui_reminder DURATION=0 # stop the recursive pause alarm 268 | 269 | _proc_end function=_stop_ui_reminder 270 | 271 | #-------------------------------------------------------------------- 272 | 273 | [delayed_gcode _ui_timeout_watchdog] # monitors overall input response time and fires when times out to respond with default - != reminder 274 | #description 275 | initial_duration: 0 276 | gcode: 277 | _proc_start function=_ui_timeout_watchdog func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 278 | 279 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 280 | 281 | # we've timed out, so basically kill ui input stuff and call the exception handler proc w/o parameters 282 | # one can sniff svv for called parameters if needed 283 | _stop_ui_input_loop 284 | _stop_ui_timeout_watchdog 285 | _stop_ui_reminder 286 | 287 | # svv.ui_input_to_cycl_def defaults to a -1 if not passed 288 | {% if svv.ui_input_to_cycl_def|int >= 1 %} 289 | SAVE_VARIABLE VARIABLE=ui_input_to_cycl_def VALUE={svv.ui_input_to_cycl_def|int - 1} 290 | {% endif %} 291 | 292 | # send ui_input_to_resp_def to ui_input_rcvr_macro if timeout count reaches exactly 0 293 | {% if svv.ui_input_to_cycl_def|int == 0 %} 294 | 295 | 296 | #woops, I think I forgot to edit this to reflect a default message vs. input hinting - lol - too many hours at it, I guess. 297 | 298 | # #annunciating that default response is being used due to timeout thresholds/cycle counts tripping 299 | # {% set emission = "-------------------------------
" ~ 300 | # "" ~ hint_type ~ " input needed (w/in " ~ ui_input_to_period ~ "s): " ~ ui_hint ~ "
" ~ 301 | # "" ~ ui_input_prompt ~ "
" ~ 302 | # "-------------------------------
" %} 303 | # 304 | # M118 {emission} 305 | # 306 | # send default response 307 | _annunciate_good_input 308 | # should I better mock UI by calling processing (bypass validation as default is programmatically asserted.?.) 309 | #fixme? 310 | #{svv.ui_input_rcvr_macro} UI_INPUT='"{svv.ui_input_to_resp_def|replace("\"","\\\"")}"' 311 | _process_user_input UI_INPUT='"{svv.ui_input_to_resp_def|replace("\"","\\\"")}"' 312 | {% else %} 313 | # call the to exception handler proc - codified below to default to the _ui_exception_handler macro 314 | {svv.ui_input_excpt_hdlr} 315 | {% endif %} 316 | 317 | _proc_end function=_ui_timeout_watchdog 318 | 319 | #-------------------------------------------------------------------- 320 | 321 | [gcode_macro _start_ui_timeout_watchdog] # starts the watchdog - trigger at begining of input cycle 322 | description: Start the User Input Timeout Watchdog - param DURATION sets period 323 | gcode: 324 | 325 | _proc_start function=_start_ui_timeout_watchdog func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 326 | 327 | UPDATE_DELAYED_GCODE ID=_ui_timeout_watchdog DURATION={params.DURATION|int} # start a recursive pause alarm 328 | 329 | _proc_end function=_start_ui_timeout_watchdog 330 | 331 | #-------------------------------------------------------------------- 332 | 333 | [gcode_macro _stop_ui_timeout_watchdog] # stops the timeout watchdog - triggered at a timeout and also when input is received 334 | gcode: 335 | 336 | _proc_start function=_stop_ui_timeout_watchdog func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 337 | 338 | UPDATE_DELAYED_GCODE ID=_ui_timeout_watchdog DURATION=0 # stop the recursive pause alarm 339 | 340 | _proc_end function=_stop_ui_timeout_watchdog 341 | 342 | #-------------------------------------------------------------------- 343 | 344 | [delayed_gcode _await_user_input] # this is the working delayed gcode that tests for input being received (leans on svv a lot...) 345 | #description 346 | # RCVR_MACRO = macro to run upon validated input 347 | # can't pass dynamic parameters to delayed_gcode? so need to load RCVR_MACRO from saved variables 348 | # had to make use of namespace workaround for in-proc flag tracking 349 | initial_duration: 0 350 | gcode: 351 | 352 | # conditionally muting trace emissions for now, given how chatty this proc is (running every 0.5S...) 353 | {% if printer["gcode_macro _ui_vars"].debug_trace_emissions %} 354 | _proc_start function=_await_user_input func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 355 | {% endif %} 356 | 357 | {% set this = namespace(no_ui_input=1) %} # work around for change visibility in same proc instance 358 | 359 | # this is for the case when a user enters a string that contains spaces or w/e 360 | {% set this2 = namespace(ui_string_flag=0) %} # work around for change visibility in same proc instance 361 | 362 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 363 | 364 | {% set UI_Input = "Um, no UI_INPUT passed" %} 365 | 366 | # These here are checks to see if input received 367 | # TODO: consider using afi code to dynamically process the user input and simplify the code... 368 | 369 | # FYSA: Logically, Buttons return a STRING value, in addition to having a CLICK state flag tripped 370 | # this conditional set serves to limit focus to the TYPE expected - that way a stale integer/float entry 371 | # doesn't get associated as a STRING value and vice versa 372 | {% if svv.ui_input_type|upper == "STRING" %} 373 | 374 | {% if svv.ui_click_lt %} 375 | {% set this.no_ui_input = 0 %} #clear flag 376 | {% set UI_Input = "LT" %} 377 | {% endif %} 378 | 379 | {% if svv.ui_click_rt %} 380 | {% set this.no_ui_input = 0 %} #clear flag 381 | {% set UI_Input = "RT" %} 382 | {% endif %} 383 | 384 | {% if svv.ui_click_up %} 385 | {% set this.no_ui_input = 0 %} #clear flag 386 | {% set UI_Input = "UP" %} 387 | {% endif %} 388 | 389 | {% if svv.ui_click_dn %} 390 | {% set this.no_ui_input = 0 %} #clear flag 391 | {% set UI_Input = "DN" %} 392 | {% endif %} 393 | 394 | {% if svv.ui_click_yes %} 395 | {% set this.no_ui_input = 0 %} #clear flag 396 | {% set UI_Input = "YES" %} 397 | {% endif %} 398 | 399 | {% if svv.ui_click_no %} 400 | {% set this.no_ui_input = 0 %} #clear flag 401 | {% set UI_Input = "NO" %} 402 | {% endif %} 403 | 404 | {% if svv.ui_click_conf %} 405 | {% set this.no_ui_input = 0 %} #clear flag 406 | {% set UI_Input = "CONFIRM" %} 407 | {% endif %} 408 | 409 | {% if svv.ui_click_canx %} 410 | {% set this.no_ui_input = 0 %} #clear flag 411 | {% set UI_Input = "CANCEL" %} 412 | {% endif %} 413 | 414 | {% if svv.ui_user_input_string != "" %} 415 | {% set this.no_ui_input = 0 %} #clear flag 416 | {% set UI_Input = svv.ui_user_input_string %} 417 | {% set this2.ui_string_flag = 1 %} #set flag to pass '"string"' vs. string (raw) 418 | {% endif %} 419 | 420 | {% elif svv.ui_input_type|upper == "INTEGER" %} 421 | 422 | {% if svv.ui_user_input_integer != -9999999 %} 423 | {% set this.no_ui_input = 0 %} #clear flag 424 | {% set UI_Input = svv.ui_user_input_integer %} 425 | {% endif %} 426 | 427 | {% elif svv.ui_input_type|upper == "FLOAT" %} 428 | 429 | {% if svv.ui_user_input_float != -9999999.99 %} 430 | {% set this.no_ui_input = 0 %} #clear flag 431 | {% set UI_Input = svv.ui_user_input_float %} 432 | {% endif %} 433 | 434 | {% endif %} 435 | 436 | {% if this.no_ui_input == 1 %} 437 | 438 | # No User Input so continue to recurse until to is reached or user input is provided 439 | UPDATE_DELAYED_GCODE ID=_await_user_input DURATION={printer["gcode_macro _ui_vars"].ui_input_check_recurse_period} SHH=1 # mute chatty logging 440 | 441 | {% else %} 442 | 443 | # ok, we got some input so kill timeout & reminder beeps 444 | _stop_ui_timeout_watchdog 445 | _stop_ui_reminder 446 | 447 | # fixme? does this need to kill itself, since we did get some input - need to peel back the gray matter fog... 448 | 449 | # validate user input for TYPE and BOUNDS_LO/BOUNDS_HI conformance 450 | _validate_user_input # if input is deemed 'faulty' (type or bounds escape), this macro flags via svv.ui_bad_input 451 | 452 | #enable_code_trace 453 | _process_user_input UI_INPUT='{UI_Input}' IS_STRING={this2.ui_string_flag} 454 | #disable_code_trace 455 | 456 | {% endif %} 457 | 458 | # conditionally muting trace emissions for now, given how chatty this proc is (running every 0.5S...) 459 | {% if printer["gcode_macro _ui_vars"].debug_trace_emissions %} 460 | _proc_end function=_await_user_input 461 | {% endif %} 462 | 463 | #-------------------------------------------------------------------- 464 | 465 | [gcode_macro _start_ui_input_loop] # this starts a recursive loop that fires every blah period to look for and flag input as being received 466 | gcode: 467 | 468 | _proc_start function=_start_ui_input_loop func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 469 | 470 | #bleep to intimate that it is waiting for input 471 | M300 P100 S3000 472 | # say_wait S="Awaiting Input" 473 | UPDATE_DELAYED_GCODE ID=_await_user_input DURATION={printer["gcode_macro _ui_vars"].ui_input_check_recurse_period} # start a recursive pause alarm 474 | 475 | _proc_end function=_start_ui_input_loop 476 | 477 | #-------------------------------------------------------------------- 478 | 479 | [gcode_macro _stop_ui_input_loop] # this stops the recursive every blah period loop that looks for and flags an input as being received 480 | gcode: 481 | 482 | _proc_start function=_stop_ui_input_loop func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 483 | 484 | UPDATE_DELAYED_GCODE ID=_await_user_input DURATION=0 # stop the recursive pause alarm 485 | 486 | _proc_end function=_stop_ui_input_loop 487 | 488 | #-------------------------------------------------------------------- 489 | 490 | [delayed_gcode GET_USER_INPUT_DELAYED] # delayed gcode to implement the GET_USER_INPUT_DELAY macro 491 | #description: allows for delayed utilization of GET_USER_INPUT 492 | initial_duration: 0 493 | gcode: 494 | _proc_start function=GET_USER_INPUT_DELAYED func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 495 | 496 | #need to fetch params from svv here 497 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 498 | 499 | # may need to strip string encapsulation.?. 500 | GET_USER_INPUT {svv.ui_input_delayed_ui_params} 501 | 502 | _proc_end function=GET_USER_INPUT_DELAYED 503 | 504 | #-------------------------------------------------------------------- 505 | 506 | [gcode_macro GET_USER_INPUT_DELAY] # <----<<< MAIN MACRO w/ non-blocking Delayed Execution (param.DELAY=blah [float seconds]) 507 | description: Enables delayed utilization of GET_USER_INPUT 508 | # save rawarams to svv.ui_input_delayed_ui_params and setup delayed_gcode to fire GET_USER_INPUT_DELAYED in param.DELAY seconds 509 | gcode: 510 | 511 | _proc_start function=GET_USER_INPUT_DELAY func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 512 | 513 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 514 | 515 | {% set ui_input_delayed_ui_params = rawparams|replace("\"","\\\"") %} 516 | 517 | save_variable VARIABLE=ui_input_delayed_ui_params VALUE='"{ui_input_delayed_ui_params|string}"' 518 | 519 | UPDATE_DELAYED_GCODE ID=GET_USER_INPUT_DELAYED DURATION={params.DELAY|float} # ping user for input in DELAY seconds 520 | 521 | _proc_end function=GET_USER_INPUT_DELAY 522 | 523 | #-------------------------------------------------------------------- 524 | 525 | [gcode_macro GET_USER_INPUT] # <----<<< MAIN MACRO for parsing a GUI request, setting up stuffs, & emitting query to console 526 | description: main macro for waiting for user input - if passed, TYPE will determine required input - defaults to string of 1-255 characters 527 | # PROMPT to display in console before user input 528 | # TYPE - the TYPE of input needed - string/str, integer/int, float/flt 529 | # BOUNDS_HI - for Integer/Float TYPE, user input must be >=low and <=high 530 | # BOUNDS_LO - if string TYPE, ceiling(low)/ceiling(hi) drive char count checks 531 | # RCVR_MACRO proc to run after input received - said proc will need to query svv for values 532 | # TO_PERIOD - period in seconds to wait for user input 533 | # EXCPT_HDLR - proc to call in the event of a user input timeout 534 | 535 | # TO_CYCL_DEF - Defaults to -1 (disabled) >0 integer TO_period expiration cycles to occur before TO_DEF_RESP is sent to RCVR_MACRO 536 | # TO_RESP_DEF - if TO_CYCLES >0 this param will be passed to RCVR_MACRO via UI_INPUT if no user input received after TO_CYCL_DEF timeouts 537 | # RMDR_PERIOD - period between reminder bleeps (0 disabled) 538 | 539 | # internal parameter is used for timeout requeries - REQUERY=1 if so 540 | 541 | #TODO:s 542 | # react based on state - if idle, if printing, if paused, if error, if ... 543 | # may need to impute a pause if not already done and in a print.?. 544 | 545 | gcode: 546 | 547 | # enable_code_trace 548 | 549 | _proc_start function=get_user_input func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 550 | 551 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 552 | 553 | _ui_clear_cache 554 | 555 | {% if params.REQUERY|default(0)|int == 0 %} 556 | # clear this decremented counter var ONLY on initial query, not automated requeries 557 | # this is a countdown variable to enable automatic default responses after 558 | # n timeouts, so its value needs to survive across requeries 559 | 560 | # this is the iterations to timeout before TO_RESP_DEF is sent to RCVR_MACRO 561 | # logic elsewhere prevents TO_RESP_DEF being sent to RCVR_MACRO if TO_CYCL_DEF <0 562 | {% set ui_input_to_cycl_def = params.TO_CYCL_DEF|default(printer["gcode_macro _ui_vars"].ui_input_def_param_to_cycl_def) %} 563 | SAVE_VARIABLE VARIABLE=ui_input_to_cycl_def VALUE={ui_input_to_cycl_def} 564 | 565 | #fixme? enable the following? 566 | # disallow a 0 value here, force to 1 if 0 is passed? 567 | # {% if svv.ui_input_to_cycl_def == 0 %} 568 | # SAVE_VARIABLE VARIABLE=ui_input_to_cycl_def VALUE={ui_input_to_cycl_def} 569 | # {% endif %} 570 | 571 | {% endif %} 572 | 573 | # store temp targets at entry, if planning to timeout the HE/Bed 574 | {% set ui_entry_he_temp = svv.extruder_temp_setting %} 575 | {% set ui_entry_bed_temp = svv.heater_bed_temp_setting %} 576 | 577 | # catch klipper printer state on entry for conditionals below, when implemented. 578 | {% if printer.idle_timeout.state == "Printing" %} 579 | # force a pause 580 | # set timeouts for HE and Bed 581 | {% endif %} 582 | 583 | {% set ui_input_prompt = params.PROMPT|default(printer["gcode_macro _ui_vars"].ui_input_def_param_prompt) %} 584 | SAVE_VARIABLE VARIABLE=ui_input_prompt VALUE='"{ui_input_prompt}"' 585 | 586 | {% set ui_input_rcvr_macro = params.RCVR_MACRO|default(printer["gcode_macro _ui_vars"].ui_input_def_param_rcvr_macro) %} 587 | SAVE_VARIABLE VARIABLE=ui_input_rcvr_macro VALUE='"{ui_input_rcvr_macro}"' 588 | 589 | {% set ui_input_type = params.TYPE|default(printer["gcode_macro _ui_vars"].ui_input_def_param_type) %} 590 | 591 | # allow for abbreviations - int/integer, str/string, flt/float 592 | {% if ui_input_type|upper == "STR" %} 593 | {% set ui_input_type = "STRING" %} 594 | {% elif ui_input_type|upper == "INT" %} 595 | {% set ui_input_type = "INTEGER" %} 596 | {% elif ui_input_type|upper == "FLT" %} 597 | {% set ui_input_type = "FLOAT" %} 598 | {% endif %} 599 | 600 | SAVE_VARIABLE VARIABLE=ui_input_type VALUE='"{ui_input_type|upper}"' 601 | 602 | {% set ui_input_to_period = params.TO_PERIOD|default(printer["gcode_macro _ui_vars"].ui_input_def_param_to_period)|int %} 603 | SAVE_VARIABLE VARIABLE=ui_input_to_period VALUE={ui_input_to_period} 604 | 605 | {% set ui_input_excpt_hdlr = params.EXCPT_HDLR|default(printer["gcode_macro _ui_vars"].ui_input_def_param_excpt_hdlr) %} 606 | SAVE_VARIABLE VARIABLE=ui_input_excpt_hdlr VALUE='"{ui_input_excpt_hdlr}"' 607 | 608 | {% set ui_input_to_resp_def = params.TO_RESP_DEF|default(printer["gcode_macro _ui_vars"].ui_input_def_param_to_resp_def) %} 609 | SAVE_VARIABLE VARIABLE=ui_input_to_resp_def VALUE='"{ui_input_to_resp_def}"' 610 | 611 | {% if ui_input_type|upper == "STRING" %} 612 | {% set ui_dflt_bnd_lo = printer["gcode_macro _ui_vars"].ui_input_def_param_bounds_lo_str %} 613 | {% set ui_dflt_bnd_hi = printer["gcode_macro _ui_vars"].ui_input_def_param_bounds_hi_str %} 614 | {% else %} 615 | {% set ui_dflt_bnd_lo = printer["gcode_macro _ui_vars"].ui_input_def_param_bounds_lo_num %} 616 | {% set ui_dflt_bnd_hi = printer["gcode_macro _ui_vars"].ui_input_def_param_bounds_hi_num %} 617 | {% endif %} 618 | 619 | {% set ui_input_bounds_lo = params.BOUNDS_LO|default(ui_dflt_bnd_lo) %} 620 | SAVE_VARIABLE VARIABLE=ui_input_bounds_lo VALUE={ui_input_bounds_lo} 621 | 622 | {% set ui_input_bounds_hi = params.BOUNDS_HI|default(ui_dflt_bnd_hi) %} 623 | SAVE_VARIABLE VARIABLE=ui_input_bounds_hi VALUE={ui_input_bounds_hi} 624 | 625 | {% set ui_input_rmdr_period = params.RMDR_PERIOD|default(printer["gcode_macro _ui_vars"].ui_input_def_param_rmdr_period) %} 626 | SAVE_VARIABLE VARIABLE=ui_input_rmdr_period VALUE={ui_input_rmdr_period} 627 | 628 | {% set ui_input_macro_rawparams = rawparams|replace("\"","\\\"") %} 629 | SAVE_VARIABLE VARIABLE=ui_input_macro_rawparams VALUE='"{ui_input_macro_rawparams}"' 630 | 631 | SAVE_VARIABLE VARIABLE=ui_input_rcvr_macro VALUE='"{ui_input_rcvr_macro}"' 632 | 633 | {% if printer["gcode_macro _ui_vars"].ui_enable_input_hints %} 634 | 635 | {% if ui_input_type|upper == "STRING" %} 636 | {% set hint_type = "String" %} 637 | {% set ui_hint = ui_input_bounds_lo|float|round(0, 'floor')|int ~ " <= length <= " ~ ui_input_bounds_hi|float|round(0, 'ceil')|int %} 638 | {% elif ui_input_type|upper == "INTEGER" %} 639 | {% set hint_type = "Integer" %} 640 | {% set ui_hint = ui_input_bounds_lo|float|round(0, 'floor')|int ~ " <= value <= " ~ ui_input_bounds_hi|float|round(0, 'ceil')|int %} 641 | {% else %} # float 642 | {% set hint_type = "Float" %} 643 | {% set ui_hint = ui_input_bounds_lo|float ~ " <= value <= " ~ ui_input_bounds_hi|float %} 644 | {% endif %} 645 | 646 | {% set emission = "-------------------------------
" ~ 647 | "" ~ hint_type ~ " input needed (w/in " ~ ui_input_to_period ~ "s): " ~ ui_hint ~ "
" ~ 648 | "" ~ ui_input_prompt ~ "
" ~ 649 | "-------------------------------
" %} 650 | 651 | M118 {emission} 652 | 653 | {% else %} 654 | 655 | M118 { "-------------------------------
" ~ ui_input_prompt ~ "
-------------------------------
" } 656 | 657 | {% endif %} 658 | 659 | # likely need to pause and then set timeouts for HE and Bed, or if flagged to timeout, 660 | 661 | _start_ui_timeout_watchdog DURATION={ui_input_to_period} # set input timeout watchdog 662 | _start_ui_input_loop # start input loop immediately 663 | 664 | {% if printer["gcode_macro _ui_vars"].ui_reminder_enable %} 665 | _start_ui_reminder 666 | {% endif %} 667 | 668 | # do we need to block or non_blocking_stall here??? 669 | # or not block but rather carry on??? 670 | 671 | _proc_end function=get_user_input 672 | 673 | # disable_code_trace 674 | 675 | #-------------------------------------------------------------------- 676 | 677 | [gcode_macro _validate_user_input] # performs tests on input received to flag it as good or bad 678 | description: does simple bounds checking based on passed TYPE requirements 679 | # the following user input bounds parameters are stored in svv 680 | # ui_input_type - def to string (used to for string inputs as well as buttons) 681 | # ui_input_bounds_lo - for int/flt, lowest value to get, for str, lowest # of characters - def to 1 682 | # ui_input_bounds_hi - for int/flt, highest value to get, for str, highest # of characters - def to 255 683 | # ui_input_excpt_hdlr - bad input is reacted to in the same manner as a timeout, with a flag set: svv.ui_bad_input 684 | # ui_input_to_cycl_def - timeout iterations before resp_def sent to rcvr_macro 685 | # ui_input_to_resp_def - if ui_input_to_cycl_def is > 0 then this is passed on final timeout 686 | gcode: 687 | 688 | _proc_start function=_validate_user_input func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 689 | 690 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 691 | 692 | {% if svv.ui_input_type|upper == "STRING" 693 | and svv.ui_user_input_string != "" 694 | and svv.ui_user_input_string|string|length >= svv.ui_input_bounds_lo|round(0, 'floor') 695 | and svv.ui_user_input_string|string|length <= svv.ui_input_bounds_hi|round(0, 'ceil') %} 696 | 697 | _annunciate_good_input 698 | 699 | {% elif svv.ui_input_type|upper == "INTEGER" 700 | and svv.ui_user_input_integer >= svv.ui_input_bounds_lo|round(0, 'floor')|int 701 | and svv.ui_user_input_integer <= svv.ui_input_bounds_hi|round(0, 'ceil')|int %} 702 | 703 | _annunciate_good_input 704 | 705 | {% elif svv.ui_input_type|upper == "FLOAT" 706 | and svv.ui_user_input_float >= svv.ui_input_bounds_lo|float 707 | and svv.ui_user_input_float <= svv.ui_input_bounds_hi|float %} 708 | 709 | _annunciate_good_input 710 | 711 | {% else %} 712 | 713 | # if we get here, the input did not pass simple bounds checks - so initiate an 'exception' 714 | 715 | # first stop stuffs so toes don't get stepped on 716 | _stop_ui_input_loop 717 | _stop_ui_timeout_watchdog 718 | _stop_ui_reminder 719 | 720 | SAVE_VARIABLE VARIABLE=ui_bad_input VALUE=1 721 | 722 | # call the to exception handler proc - codified below to default to the _ui_exception_handler macro 723 | {svv.ui_input_excpt_hdlr} 724 | 725 | {% endif %} 726 | 727 | _proc_end function=_validate_user_input 728 | 729 | #-------------------------------------------------------------------- 730 | 731 | [gcode_macro _process_user_input] # processes user input as either faulty (for requery), or passes onto receiver macro 732 | description: had to split this out of _await_user_input for svv.ui_bad_input visibility purposes 733 | gcode: 734 | 735 | # enable_code_trace 736 | 737 | _proc_start function=_process_user_input func_params={rawparams|string} 738 | 739 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 740 | 741 | {% if svv.ui_bad_input != 0 %} 742 | # bad input received, so need to re-call get_user_input macro which 743 | # clears cache & resets the bad input flag) so no endless loop concerns 744 | get_user_input {svv.ui_input_macro_rawparams} 745 | {% else %} 746 | # input is not bad, so need to process it as good input, calling the 747 | # RCVR_MACRO proc passed by name, passing UI_INPUT as a parameter, 748 | # wrapping in quotes if needed, and escaping embedded quotes 749 | {% if params.IS_STRING|default(0)|int == 0 %} 750 | {svv.ui_input_rcvr_macro} UI_INPUT={params.UI_INPUT} # numerical input 751 | {% else %} # if a string was entered, make sure to stringify it and escape any embedded quote marks 752 | # {svv.ui_input_rcvr_macro} UI_INPUT='"{params.UI_INPUT}"' # string input 753 | {svv.ui_input_rcvr_macro} UI_INPUT='"{params.UI_INPUT|replace("\"","\\\"")}"' 754 | {% endif %} 755 | {% endif %} 756 | 757 | _proc_end function=_process_user_input 758 | 759 | # disable_code_trace 760 | 761 | #-------------------------------------------------------------------- 762 | 763 | [gcode_macro _ui_exception_handler] # default exception handler macro - can be used as a template for other user input exception handlers 764 | description: default macro called in the event of a user input timeout or out of bounds/bad user entry 765 | gcode: 766 | 767 | _proc_start function=_ui_exception_handler func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 768 | 769 | _annunciate_input_exception 770 | 771 | {% set svv = printer.save_variables.variables %} # set context for save_variables object 772 | 773 | {% set exception_hint = "Exception - " %} 774 | 775 | {% if printer["gcode_macro _ui_vars"].ui_disable_exception_hints == 0 %} 776 | 777 | {% if svv.ui_bad_input != 0 %} 778 | {% set exception_hint = exception_hint ~ "No/Bad Input" %} 779 | {% else %} 780 | {% set exception_hint = exception_hint ~ svv.ui_input_to_period ~ "s timeout" %} 781 | {% endif %} 782 | 783 | {% set exception_hint = exception_hint ~ " - expecting " ~ svv.ui_input_type ~ " " %} 784 | 785 | {% if svv.ui_input_type|upper == "STRING" %} 786 | {% set exception_hint = exception_hint ~ "of " ~ svv.ui_input_bounds_lo|round(0, 'floor')|int ~ " to " ~ svv.ui_input_bounds_hi|round(0, 'ceil')|int ~ " chars." %} 787 | {% elif svv.ui_input_type|upper == "INTEGER" %} 788 | {% set exception_hint = exception_hint ~ "between " ~ svv.ui_input_bounds_lo|round(0, 'floor')|int ~ " to " ~ svv.ui_input_bounds_hi|round(0, 'ceil')|int ~ " (inclusive)." %} 789 | {% else %} # float 790 | {% set exception_hint = exception_hint ~ "between " ~ svv.ui_input_bounds_lo|float ~ " to " ~ svv.ui_input_bounds_hi|float ~ " (inclusive)." %} 791 | {% endif %} 792 | 793 | {% set exception_hint = exception_hint ~ " " %} 794 | 795 | {% endif %} 796 | 797 | {% set exception_hint = exception_hint ~ "Please try again." %} 798 | 799 | M118 { exception_hint } 800 | 801 | {% if svv.ui_bad_input != 0 %} 802 | # so we are in a bad input state, so just return and let the calling proc react to bad input 803 | {% else %} 804 | # so this is a timeout exception and not a bad input, thus not a validation exception 805 | # so go ahead and re-call again to give user another opportunity to give the input 806 | # the REQUERY=1 is important to carry over to user exception handlers so that default timeout code works like it should 807 | get_user_input REQUERY=1 {svv.ui_input_macro_rawparams} 808 | {% endif %} 809 | 810 | _proc_end function=_ui_exception_handler 811 | 812 | #-------------------------------------------------------------------- 813 | 814 | [gcode_macro _annunciate_good_input] # audible feedback of an acceptable input 815 | # also stops reminders and recursive loops 816 | gcode: 817 | 818 | _proc_start function=_annunciate_good_input func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 819 | 820 | M3002 S2000 821 | M3002 S3000 822 | M300.1 823 | 824 | _proc_end function=_annunciate_good_input 825 | 826 | #-------------------------------------------------------------------- 827 | 828 | [gcode_macro _annunciate_input_exception] # audible feedback of an unacceptable input 829 | gcode: 830 | 831 | _proc_start function=_annunciate_input_exception func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 832 | 833 | M300 P750 S350 834 | 835 | _proc_end function=_annunciate_input_exception 836 | 837 | #-------------------------------------------------------------------- 838 | 839 | [gcode_macro _test_show_user_input] # default receiver macro - just echos user input to console... 840 | description: test macro to display passed user input, if any 841 | #UI_INPUT 842 | gcode: 843 | _proc_start function=_test_show_user_input func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 844 | 845 | {% set ui_input = params.UI_INPUT|default("Um, NO UI_INPUT passed.!. Why? How?") %} 846 | M118 {"Received Input: " ~ ui_input ~ "

"} 847 | 848 | _proc_end function=_test_show_user_input 849 | 850 | #-------------------------------------------------------------------- 851 | 852 | [gcode_macro _ui_clear_cache] # Inits all ui_ vars in the svv variable file - fires at module start, and with a new query 853 | description: helper proc to wipe/initialize svv contents related to user input 854 | gcode: 855 | _proc_start function=_ui_clear_cache func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 856 | 857 | SAVE_VARIABLE VARIABLE=ui_bad_input VALUE=0 858 | 859 | SAVE_VARIABLE VARIABLE=ui_click_up VALUE=0 860 | SAVE_VARIABLE VARIABLE=ui_click_dn VALUE=0 861 | SAVE_VARIABLE VARIABLE=ui_click_lt VALUE=0 862 | SAVE_VARIABLE VARIABLE=ui_click_rt VALUE=0 863 | SAVE_VARIABLE VARIABLE=ui_click_yes VALUE=0 864 | SAVE_VARIABLE VARIABLE=ui_click_no VALUE=0 865 | SAVE_VARIABLE VARIABLE=ui_click_conf VALUE=0 866 | SAVE_VARIABLE VARIABLE=ui_click_canx VALUE=0 867 | 868 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='""' 869 | SAVE_VARIABLE VARIABLE=ui_user_input_integer VALUE=-9999999 870 | SAVE_VARIABLE VARIABLE=ui_user_input_float VALUE=-9999999.99 871 | SAVE_VARIABLE VARIABLE=ui_user_input_raw VALUE='""' 872 | 873 | SAVE_VARIABLE VARIABLE=ui_input_prompt VALUE='""' 874 | SAVE_VARIABLE VARIABLE=ui_input_type VALUE='""' 875 | SAVE_VARIABLE VARIABLE=ui_input_bounds_lo VALUE=-9999999 876 | SAVE_VARIABLE VARIABLE=ui_input_bounds_hi VALUE=9999999 877 | SAVE_VARIABLE VARIABLE=ui_input_rcvr_macro VALUE='""' 878 | SAVE_VARIABLE VARIABLE=ui_input_to_period VALUE=0 879 | SAVE_VARIABLE VARIABLE=ui_input_excpt_hdlr VALUE='""' 880 | 881 | ## SAVE_VARIABLE VARIABLE=ui_input_to_cycl_def VALUE=-1 Dealing with this separately as it needs to survive across requeries 882 | SAVE_VARIABLE VARIABLE=ui_input_to_resp_def VALUE='""' 883 | 884 | SAVE_VARIABLE VARIABLE=ui_input_macro_rawparams VALUE='""' 885 | SAVE_VARIABLE VARIABLE=ui_input_delayed_ui_params VALUE='""' 886 | 887 | _proc_end function=_ui_clear_cache 888 | 889 | #-------------------------------------------------------------------- 890 | 891 | [gcode_macro UP] # for UI button - sets UP button stroked context in svv variables 892 | description: up button for user to stroke 893 | gcode: 894 | _proc_start function=UP func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 895 | SAVE_VARIABLE VARIABLE=ui_click_up VALUE=1 896 | 897 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 898 | say_wait S="Up" 899 | {% endif %} 900 | 901 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"UP"' 902 | _proc_end function=UP 903 | 904 | #-------------------------------------------------------------------- 905 | 906 | [gcode_macro DN] # for UI button - sets DN button stroked context in svv variables 907 | description: dn button for user to stroke 908 | gcode: 909 | _proc_start function=DN func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 910 | SAVE_VARIABLE VARIABLE=ui_click_dn VALUE=1 911 | 912 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 913 | say_wait S="Down" 914 | {% endif %} 915 | 916 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"DN"' 917 | _proc_end function=DN 918 | 919 | #-------------------------------------------------------------------- 920 | 921 | [gcode_macro LT] # for UI button - sets LT button stroked context in svv variables 922 | description: LT button for user to stroke 923 | gcode: 924 | _proc_start function=LT func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 925 | SAVE_VARIABLE VARIABLE=ui_click_lt VALUE=1 926 | 927 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 928 | say_wait S="Left" 929 | {% endif %} 930 | 931 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"LT"' 932 | _proc_end function=LT 933 | 934 | #-------------------------------------------------------------------- 935 | 936 | [gcode_macro RT] # for UI button - sets RT button stroked context in svv variables 937 | description: rt button for user to stroke 938 | gcode: 939 | _proc_start function=RT func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 940 | SAVE_VARIABLE VARIABLE=ui_click_rt VALUE=1 941 | 942 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 943 | say_wait S="Right" 944 | {% endif %} 945 | 946 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"RT"' 947 | _proc_end function=RT 948 | 949 | #-------------------------------------------------------------------- 950 | 951 | [gcode_macro YES] # for UI button - sets YES button stroked context in svv variables 952 | description: yes button for user to stroke 953 | gcode: 954 | _proc_start function=YES func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 955 | SAVE_VARIABLE VARIABLE=ui_click_yes VALUE=1 956 | 957 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 958 | say_wait S="Yes" 959 | {% endif %} 960 | 961 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"YES"' 962 | _proc_end function=YES 963 | 964 | #-------------------------------------------------------------------- 965 | 966 | [gcode_macro NO] # for UI button - sets NO button stroked context in svv variables 967 | description: no button for user to stroke 968 | gcode: 969 | _proc_start function=NO func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 970 | SAVE_VARIABLE VARIABLE=ui_click_no VALUE=1 971 | 972 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 973 | say_wait S="No" 974 | {% endif %} 975 | 976 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"NO"' 977 | _proc_end function=NO 978 | 979 | #-------------------------------------------------------------------- 980 | 981 | [gcode_macro CONFIRM] # for UI button - sets CONFIRM button stroked context in svv variables 982 | description: confirm button for user to stroke 983 | gcode: 984 | 985 | _proc_start function=CONFIRM func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 986 | SAVE_VARIABLE VARIABLE=ui_click_conf VALUE=1 987 | 988 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 989 | say_wait S="Confirm" 990 | {% endif %} 991 | 992 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"CONFIRM"' 993 | _proc_end function=CONFIRM 994 | 995 | #-------------------------------------------------------------------- 996 | 997 | [gcode_macro CANCEL] # for UI button - sets CANCEL button stroked context in svv variables 998 | description: cancel button for user to stroke 999 | gcode: 1000 | 1001 | _proc_start function=CANCEL func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 1002 | SAVE_VARIABLE VARIABLE=ui_click_canx VALUE=1 1003 | 1004 | {% if printer["gcode_macro _ui_vars"].ui_button_readback_flag %} 1005 | say_wait S="Cancel" 1006 | {% endif %} 1007 | 1008 | SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"CANCEL"' 1009 | _proc_end function=CANCEL 1010 | 1011 | #-------------------------------------------------------------------- 1012 | 1013 | [gcode_macro USER_ENTRY] # for UI button macro for user to enter integer/float numbers and QUOTED strings 1014 | description: macro for user to enter integer/float numbers and QUOTED strings 1015 | gcode: 1016 | 1017 | _proc_start function=USER_ENTRY func_params='"{rawparams|string|replace("\"","`")|replace("'","`")}"' 1018 | 1019 | {% set ui_user_input_string = params.QUOTED_STRING|default("") %} 1020 | {% set ui_user_input_float = params.FLOAT|default(-9999999.99)|float %} 1021 | {% set ui_user_input_integer = params.INTEGER|default(-9999999)|int %} 1022 | 1023 | {% set ui_user_input_raw = rawparams|replace("\"","\\\"") %} 1024 | SAVE_VARIABLE VARIABLE=ui_user_input_raw VALUE='"{ui_user_input_raw}"' 1025 | 1026 | {% if ui_user_input_string != "" %} 1027 | #SAVE_VARIABLE VARIABLE=ui_user_input_string VALUE='"{ui_user_input_string|replace("\"","\\\"")}"' 1028 | #wrappered function had a specific behavior of not saving stringified numbers as strings, by design 1029 | #thus disabling and forcing natural behavior by calling organic klipper function directly 1030 | _SAVE_VARIABLE_STOCK VARIABLE=ui_user_input_string VALUE='"{ui_user_input_string|replace("\"","\\\"")}"' 1031 | 1032 | {% if printer["gcode_macro _ui_vars"].ui_input_readback_flag %} 1033 | say_wait S="You entered String value of: {ui_user_input_string|replace("\"","\\\"")}" 1034 | {% endif %} 1035 | {% endif %} 1036 | 1037 | {% if ui_user_input_integer != -9999999 %} 1038 | SAVE_VARIABLE VARIABLE=ui_user_input_integer VALUE={ui_user_input_integer} 1039 | {% if printer["gcode_macro _ui_vars"].ui_input_readback_flag %} 1040 | say_wait S="You entered Integer value of {ui_user_input_integer}" 1041 | {% endif %} 1042 | {% endif %} 1043 | 1044 | {% if ui_user_input_float != -9999999.99 %} 1045 | SAVE_VARIABLE VARIABLE=ui_user_input_float VALUE={ui_user_input_float} 1046 | {% if printer["gcode_macro _ui_vars"].ui_input_readback_flag %} 1047 | say_wait S="You entered Float value of {ui_user_input_float}" 1048 | {% endif %} 1049 | {% endif %} 1050 | 1051 | _proc_end function=USER_ENTRY 1052 | 1053 | #-------------------------------------------------------------------- 1054 | 1055 | [gcode_macro _null] # may have been for dev testing - not currently used, I don't believe - may nix this... 1056 | description: UNTESTED Dummy macro to have be a null receiver - just saves UI_INPUT to svv 1057 | gcode: 1058 | 1059 | _proc_start function=_null func_params='"{rawparams|string|replace("\\\"", "")|replace("\\\'", "")|replace("\"", "")|replace("\'", "")}"' 1060 | 1061 | {% set ui_last_input = params.UI_INPUT|replace("\"","\\\"") %} 1062 | SAVE_VARIABLE VARIABLE=ui_last_input VALUE='"{ui_last_input}"' 1063 | 1064 | _proc_end function=_null 1065 | -------------------------------------------------------------------------------- /pics/MacroTitles&Comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TodWulff/Klipper_UserInteraction/a6b427c23d5bf60c01d5aae0878c6c594abb4748/pics/MacroTitles&Comments.png --------------------------------------------------------------------------------