Commit | Line | Data |
---|---|---|
399cb110 | 1 | /* |
35089dc7 JM |
2 | This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl). |
3 | Smoothie is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. | |
4 | Smoothie is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |
399cb110 | 5 | You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>. |
35089dc7 JM |
6 | */ |
7 | ||
8 | #include "libs/Kernel.h" | |
9 | #include "Panel.h" | |
ca6effd7 JM |
10 | #include "PanelScreen.h" |
11 | ||
35089dc7 JM |
12 | #include "libs/nuts_bolts.h" |
13 | #include "libs/utils.h" | |
35089dc7 | 14 | #include "Button.h" |
fb877329 JM |
15 | #include "libs/USBDevice/USBMSD/SDCard.h" |
16 | #include "libs/SDFAT.h" | |
ca6effd7 | 17 | |
35089dc7 | 18 | #include "modules/utils/player/PlayerPublicAccess.h" |
ca6effd7 JM |
19 | #include "screens/CustomScreen.h" |
20 | #include "screens/MainMenuScreen.h" | |
61134a65 JM |
21 | #include "SlowTicker.h" |
22 | #include "Gcode.h" | |
23 | #include "Pauser.h" | |
cee1bb2d JM |
24 | #include "TemperatureControlPublicAccess.h" |
25 | #include "ModifyValuesScreen.h" | |
26 | #include "PublicDataRequest.h" | |
61134a65 | 27 | #include "PublicData.h" |
fb877329 JM |
28 | #include "StreamOutputPool.h" |
29 | #include "platform_memory.h" | |
35089dc7 | 30 | |
357d0eda | 31 | #include "panels/ReprapDiscountGLCD.h" |
84e7fd5a | 32 | #include "panels/ST7565.h" |
3ae0e673 JM |
33 | #include "panels/UniversalAdapter.h" |
34 | ||
f140cf3a | 35 | #include "version.h" |
7af0714f | 36 | #include "checksumm.h" |
8d54c34c | 37 | #include "ConfigValue.h" |
7713b1ef | 38 | #include "Config.h" |
35089dc7 | 39 | |
fb877329 JM |
40 | // for parse_pins in mbed |
41 | #include "pinmap.h" | |
42 | ||
ca6effd7 JM |
43 | #define panel_checksum CHECKSUM("panel") |
44 | #define enable_checksum CHECKSUM("enable") | |
45 | #define lcd_checksum CHECKSUM("lcd") | |
ca6effd7 JM |
46 | #define rrd_glcd_checksum CHECKSUM("reprap_discount_glcd") |
47 | #define st7565_glcd_checksum CHECKSUM("st7565_glcd") | |
8b6bc932 JM |
48 | #define viki2_checksum CHECKSUM("viki2") |
49 | #define mini_viki2_checksum CHECKSUM("mini_viki2") | |
3ae0e673 | 50 | #define universal_adapter_checksum CHECKSUM("universal_adapter") |
ca6effd7 | 51 | |
fa3f7b30 JM |
52 | #define menu_offset_checksum CHECKSUM("menu_offset") |
53 | #define encoder_resolution_checksum CHECKSUM("encoder_resolution") | |
54 | #define jog_x_feedrate_checksum CHECKSUM("alpha_jog_feedrate") | |
55 | #define jog_y_feedrate_checksum CHECKSUM("beta_jog_feedrate") | |
56 | #define jog_z_feedrate_checksum CHECKSUM("gamma_jog_feedrate") | |
f1e696a3 | 57 | #define longpress_delay_checksum CHECKSUM("longpress_delay") |
ca6effd7 | 58 | |
fb877329 JM |
59 | #define ext_sd_checksum CHECKSUM("external_sd") |
60 | #define sdcd_pin_checksum CHECKSUM("sdcd_pin") | |
61 | #define spi_channel_checksum CHECKSUM("spi_channel") | |
62 | #define spi_cs_pin_checksum CHECKSUM("spi_cs_pin") | |
63 | ||
ca6effd7 JM |
64 | #define hotend_temp_checksum CHECKSUM("hotend_temperature") |
65 | #define bed_temp_checksum CHECKSUM("bed_temperature") | |
66 | ||
cee1bb2d JM |
67 | Panel* Panel::instance= nullptr; |
68 | ||
862fc625 JM |
69 | Panel::Panel() |
70 | { | |
cee1bb2d | 71 | instance= this; |
35089dc7 JM |
72 | this->counter_changed = false; |
73 | this->click_changed = false; | |
74 | this->refresh_flag = false; | |
75 | this->enter_menu_mode(); | |
862fc625 | 76 | this->lcd = NULL; |
58d6d841 | 77 | this->do_buttons = false; |
e456d044 | 78 | this->do_encoder = false; |
862fc625 JM |
79 | this->idle_time = 0; |
80 | this->start_up = true; | |
81 | this->current_screen = NULL; | |
fb877329 JM |
82 | this->sd= nullptr; |
83 | this->extmounter= nullptr; | |
84 | this->external_sd_enable= false; | |
76217df5 | 85 | this->halted= false; |
7aa9fef3 | 86 | strcpy(this->playing_file, "Playing file"); |
35089dc7 JM |
87 | } |
88 | ||
862fc625 JM |
89 | Panel::~Panel() |
90 | { | |
58d6d841 | 91 | delete this->lcd; |
fb877329 JM |
92 | delete this->extmounter; |
93 | delete this->sd; | |
35089dc7 JM |
94 | } |
95 | ||
862fc625 JM |
96 | void Panel::on_module_loaded() |
97 | { | |
35089dc7 | 98 | // Exit if this module is not enabled |
314ab8f7 | 99 | if ( !THEKERNEL->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ) { |
58d6d841 JM |
100 | delete this; |
101 | return; | |
399cb110 | 102 | } |
35089dc7 | 103 | |
58d6d841 JM |
104 | // Initialise the LCD, see which LCD to use |
105 | if (this->lcd != NULL) delete this->lcd; | |
7713b1ef | 106 | int lcd_cksm = get_checksum(THEKERNEL->config->value(panel_checksum, lcd_checksum)->by_default("reprap_discount_glcd")->as_string()); |
58d6d841 JM |
107 | |
108 | // Note checksums are not const expressions when in debug mode, so don't use switch | |
7713b1ef | 109 | if (lcd_cksm == rrd_glcd_checksum) { |
357d0eda | 110 | this->lcd = new ReprapDiscountGLCD(); |
862fc625 | 111 | } else if (lcd_cksm == st7565_glcd_checksum) { |
84e7fd5a | 112 | this->lcd = new ST7565(); |
8b6bc932 | 113 | } else if (lcd_cksm == viki2_checksum) { |
8ed554f3 | 114 | this->lcd = new ST7565(1); // variant 1 |
8b6bc932 | 115 | } else if (lcd_cksm == mini_viki2_checksum) { |
8ed554f3 | 116 | this->lcd = new ST7565(2); // variant 2 |
3ae0e673 JM |
117 | } else if (lcd_cksm == universal_adapter_checksum) { |
118 | this->lcd = new UniversalAdapter(); | |
862fc625 | 119 | } else { |
7713b1ef JM |
120 | // no known lcd type defined |
121 | delete this; | |
58d6d841 JM |
122 | return; |
123 | } | |
124 | ||
fb877329 JM |
125 | // external sd |
126 | if(THEKERNEL->config->value( panel_checksum, ext_sd_checksum )->by_default(false)->as_bool()) { | |
127 | this->external_sd_enable= true; | |
128 | // external sdcard detect | |
129 | this->sdcd_pin.from_string(THEKERNEL->config->value( panel_checksum, ext_sd_checksum, sdcd_pin_checksum )->by_default("nc")->as_string())->as_input(); | |
130 | this->extsd_spi_channel = THEKERNEL->config->value(panel_checksum, ext_sd_checksum, spi_channel_checksum)->by_default(0)->as_number(); | |
131 | string s= THEKERNEL->config->value( panel_checksum, ext_sd_checksum, spi_cs_pin_checksum)->by_default("2.8")->as_string(); | |
132 | s= "P" + s; // Pinnames need to be Px_x | |
133 | this->extsd_spi_cs= parse_pins(s.c_str()); | |
134 | this->register_for_event(ON_SECOND_TICK); | |
135 | } | |
136 | ||
cee1bb2d JM |
137 | // these need to be called here as they need the config cache loaded as they enumerate modules |
138 | this->custom_screen= new CustomScreen(); | |
139 | setup_temperature_screen(); | |
ca6effd7 | 140 | |
58d6d841 JM |
141 | // some panels may need access to this global info |
142 | this->lcd->setPanel(this); | |
143 | ||
f65ce58f | 144 | // the number of screen lines the panel supports |
862fc625 | 145 | this->screen_lines = this->lcd->get_screen_lines(); |
399cb110 | 146 | |
19fb4629 JM |
147 | // some encoders may need more clicks to move menu, this is a divisor and is in config as it is |
148 | // an end user usability issue | |
314ab8f7 | 149 | this->menu_offset = THEKERNEL->config->value( panel_checksum, menu_offset_checksum )->by_default(0)->as_number(); |
19fb4629 | 150 | |
fa3f7b30 | 151 | // override default encoder resolution if needed |
314ab8f7 | 152 | this->encoder_click_resolution = THEKERNEL->config->value( panel_checksum, encoder_resolution_checksum )->by_default(this->lcd->getEncoderResolution())->as_number(); |
fa3f7b30 | 153 | |
58d6d841 | 154 | // load jogging feedrates in mm/min |
1ad23cd3 MM |
155 | jogging_speed_mm_min[0] = THEKERNEL->config->value( panel_checksum, jog_x_feedrate_checksum )->by_default(3000.0f)->as_number(); |
156 | jogging_speed_mm_min[1] = THEKERNEL->config->value( panel_checksum, jog_y_feedrate_checksum )->by_default(3000.0f)->as_number(); | |
157 | jogging_speed_mm_min[2] = THEKERNEL->config->value( panel_checksum, jog_z_feedrate_checksum )->by_default(300.0f )->as_number(); | |
f19a841a JM |
158 | |
159 | // load the default preset temeratures | |
1ad23cd3 MM |
160 | default_hotend_temperature = THEKERNEL->config->value( panel_checksum, hotend_temp_checksum )->by_default(185.0f )->as_number(); |
161 | default_bed_temperature = THEKERNEL->config->value( panel_checksum, bed_temp_checksum )->by_default(60.0f )->as_number(); | |
f19a841a | 162 | |
399cb110 | 163 | |
95195a29 JM |
164 | this->up_button.up_attach( this, &Panel::on_up ); |
165 | this->down_button.up_attach( this, &Panel::on_down ); | |
ab4abea9 | 166 | this->click_button.up_attach( this, &Panel::on_select ); |
95195a29 | 167 | this->back_button.up_attach( this, &Panel::on_back ); |
52500e58 | 168 | this->pause_button.up_attach( this, &Panel::on_pause ); |
35089dc7 | 169 | |
355c5796 L |
170 | |
171 | //setting longpress_delay | |
172 | int longpress_delay = THEKERNEL->config->value( panel_checksum, longpress_delay_checksum )->by_default(0)->as_number(); | |
173 | this->up_button.set_longpress_delay(longpress_delay); | |
f1e696a3 | 174 | this->down_button.set_longpress_delay(longpress_delay); |
355c5796 L |
175 | // this->click_button.set_longpress_delay(longpress_delay); |
176 | // this->back_button.set_longpress_delay(longpress_delay); | |
177 | // this->pause_button.set_longpress_delay(longpress_delay); | |
61134a65 JM |
178 | |
179 | ||
c206438f | 180 | THEKERNEL->slow_ticker->attach( 50, this, &Panel::button_tick ); |
e456d044 JM |
181 | if(lcd->encoderReturnsDelta()) { |
182 | // panel handles encoder pins and returns a delta | |
10d11dfd | 183 | THEKERNEL->slow_ticker->attach( 10, this, &Panel::encoder_tick ); |
e456d044 JM |
184 | }else{ |
185 | // read encoder pins | |
186 | THEKERNEL->slow_ticker->attach( 1000, this, &Panel::encoder_check ); | |
187 | } | |
399cb110 | 188 | |
767e7c51 JM |
189 | // Register for events |
190 | this->register_for_event(ON_IDLE); | |
191 | this->register_for_event(ON_MAIN_LOOP); | |
399cb110 | 192 | this->register_for_event(ON_GCODE_RECEIVED); |
76217df5 | 193 | this->register_for_event(ON_HALT); |
767e7c51 | 194 | |
35089dc7 | 195 | // Refresh timer |
314ab8f7 | 196 | THEKERNEL->slow_ticker->attach( 20, this, &Panel::refresh_tick ); |
35089dc7 JM |
197 | } |
198 | ||
199 | // Enter a screen, we only care about it now | |
862fc625 JM |
200 | void Panel::enter_screen(PanelScreen *screen) |
201 | { | |
383c9c1c JM |
202 | if(this->current_screen != nullptr) |
203 | this->current_screen->on_exit(); | |
204 | ||
35089dc7 JM |
205 | this->current_screen = screen; |
206 | this->reset_counter(); | |
207 | this->current_screen->on_enter(); | |
208 | } | |
209 | ||
210 | // Reset the counter | |
862fc625 JM |
211 | void Panel::reset_counter() |
212 | { | |
35089dc7 JM |
213 | *this->counter = 0; |
214 | this->counter_changed = false; | |
215 | } | |
216 | ||
217 | // Indicate the idle loop we want to call the refresh hook in the current screen | |
5ff3e912 | 218 | // called 20 times a second |
862fc625 JM |
219 | uint32_t Panel::refresh_tick(uint32_t dummy) |
220 | { | |
58d6d841 JM |
221 | this->refresh_flag = true; |
222 | this->idle_time++; | |
223 | return 0; | |
35089dc7 JM |
224 | } |
225 | ||
e93caece | 226 | // Encoder pins changed in interrupt or call from on_idle |
862fc625 JM |
227 | uint32_t Panel::encoder_check(uint32_t dummy) |
228 | { | |
e456d044 JM |
229 | // if encoder reads go through SPI like on universal panel adapter this needs to be |
230 | // done in idle loop, this is indicated by lcd->encoderReturnsDelta() | |
231 | // however when reading encoder directly it needs to be done | |
232 | // frequently, universal panel adapter will return an actual delta count so won't miss any if polled slowly | |
e93caece | 233 | // NOTE FIXME (WHY is it in menu only?) this code will not work if change is not 1,0,-1 anything greater (as in above case) will not work properly |
10d11dfd JM |
234 | static int encoder_counter = 0; // keeps track of absolute encoder position |
235 | static int last_encoder_click= 0; // last readfing of divided encoder count | |
35089dc7 | 236 | int change = lcd->readEncoderDelta(); |
58d6d841 | 237 | encoder_counter += change; |
10d11dfd JM |
238 | int clicks= encoder_counter/this->encoder_click_resolution; |
239 | int delta= clicks - last_encoder_click; // the number of clicks this time | |
240 | last_encoder_click= clicks; | |
b6e81799 | 241 | |
10d11dfd | 242 | if ( delta != 0 ) { |
58d6d841 | 243 | this->counter_changed = true; |
10d11dfd | 244 | (*this->counter) += delta; |
862fc625 | 245 | this->idle_time = 0; |
35089dc7 | 246 | } |
58d6d841 | 247 | return 0; |
35089dc7 JM |
248 | } |
249 | ||
250 | // Read and update each button | |
862fc625 JM |
251 | uint32_t Panel::button_tick(uint32_t dummy) |
252 | { | |
58d6d841 JM |
253 | this->do_buttons = true; |
254 | return 0; | |
35089dc7 JM |
255 | } |
256 | ||
e456d044 JM |
257 | // Read and update encoder |
258 | uint32_t Panel::encoder_tick(uint32_t dummy) | |
259 | { | |
260 | this->do_encoder = true; | |
261 | return 0; | |
262 | } | |
263 | ||
862fc625 | 264 | void Panel::on_gcode_received(void *argument) |
399cb110 | 265 | { |
862fc625 JM |
266 | Gcode *gcode = static_cast<Gcode *>(argument); |
267 | if ( gcode->has_m) { | |
268 | if ( gcode->m == 117 ) { // set LCD message | |
8090190d | 269 | this->message = get_arguments(gcode->get_command()); |
862fc625 | 270 | if (this->message.size() > 20) this->message = this->message.substr(0, 20); |
399cb110 | 271 | gcode->mark_as_taken(); |
76217df5 JM |
272 | }else if(gcode->m == 999 && halted) { |
273 | halted= false; | |
399cb110 JM |
274 | } |
275 | } | |
276 | } | |
277 | ||
35089dc7 | 278 | // on main loop, we can send gcodes or do anything that waits in this loop |
862fc625 JM |
279 | void Panel::on_main_loop(void *argument) |
280 | { | |
281 | if (this->current_screen != NULL) { | |
f140cf3a JM |
282 | this->current_screen->on_main_loop(); |
283 | this->lcd->on_main_loop(); | |
284 | } | |
35089dc7 JM |
285 | } |
286 | ||
c96fd70a JM |
287 | |
288 | #define ohw_logo_antipixel_width 80 | |
289 | #define ohw_logo_antipixel_height 15 | |
161164be | 290 | static const uint8_t ohw_logo_antipixel_bits[] = { |
862fc625 JM |
291 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, |
292 | 0x00, 0x00, 0x00, 0x01, 0x80, 0x0C, 0x00, 0x33, 0x18, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x5E, | |
293 | 0x80, 0x2D, 0x6B, 0x9B, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x18, 0xAB, 0xFF, 0xFF, | |
294 | 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x7B, 0xB3, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x7F, 0x80, 0x33, | |
295 | 0x78, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x81, 0xF3, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, | |
296 | 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xB3, 0x18, 0xDD, 0x98, 0xC5, 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xAD, | |
297 | 0x6B, 0x5D, 0x6B, 0x5D, 0x80, 0x73, 0x80, 0x3F, 0xFC, 0x21, 0x1B, 0x55, 0x08, 0xC5, 0x80, 0xF3, | |
298 | 0xC0, 0x3F, 0xFD, 0xAD, 0x5B, 0x49, 0x6A, 0xDD, 0x80, 0xE1, 0xC0, 0x3F, 0xFD, 0xAD, 0x68, 0xDD, | |
299 | 0x6B, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, | |
300 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF | |
c96fd70a JM |
301 | }; |
302 | ||
35089dc7 JM |
303 | // On idle things, we don't want to do shit in interrupts |
304 | // don't queue gcodes in this | |
862fc625 JM |
305 | void Panel::on_idle(void *argument) |
306 | { | |
307 | if (this->start_up) { | |
f140cf3a JM |
308 | this->lcd->init(); |
309 | ||
ebc64506 JM |
310 | Version v; |
311 | string build(v.get_build()); | |
312 | string date(v.get_build_date()); | |
313 | this->lcd->clear(); | |
862fc625 JM |
314 | this->lcd->setCursor(0, 0); this->lcd->printf("Welcome to Smoothie"); |
315 | this->lcd->setCursor(0, 1); this->lcd->printf("%s", build.substr(0, 20).c_str()); | |
316 | this->lcd->setCursor(0, 2); this->lcd->printf("%s", date.substr(0, 20).c_str()); | |
317 | this->lcd->setCursor(0, 3); this->lcd->printf("Please wait...."); | |
ebc64506 | 318 | |
862fc625 | 319 | if (this->lcd->hasGraphics()) { |
c96fd70a JM |
320 | this->lcd->bltGlyph(24, 40, ohw_logo_antipixel_width, ohw_logo_antipixel_height, ohw_logo_antipixel_bits); |
321 | } | |
322 | ||
f54d25cb JM |
323 | this->lcd->on_refresh(true); // tell lcd to display now |
324 | ||
f140cf3a | 325 | // Default top screen |
ca6effd7 | 326 | this->top_screen= new MainMenuScreen(); |
ca6effd7 | 327 | this->custom_screen->set_parent(this->top_screen); |
862fc625 | 328 | this->start_up = false; |
5ff3e912 | 329 | return; |
f140cf3a | 330 | } |
35089dc7 | 331 | |
ca6effd7 | 332 | MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen); |
58d6d841 | 333 | // after being idle for a while switch to Watch screen |
5ff3e912 | 334 | if (this->current_screen != NULL && this->idle_time > this->current_screen->idle_timeout_secs()*20) { |
862fc625 | 335 | this->idle_time = 0; |
ca6effd7 JM |
336 | if (mms->watch_screen != this->current_screen) { |
337 | this->enter_screen(mms->watch_screen); | |
58d6d841 JM |
338 | // TODO do we need to reset any state? |
339 | } | |
399cb110 | 340 | |
58d6d841 JM |
341 | return; |
342 | } | |
399cb110 | 343 | |
5ff3e912 JM |
344 | if(current_screen == NULL && this->idle_time > 20*4) { |
345 | this->enter_screen(mms->watch_screen); | |
346 | return; | |
347 | } | |
348 | ||
e456d044 JM |
349 | if(this->do_encoder) { |
350 | this->do_encoder= false; | |
351 | encoder_check(0); | |
352 | } | |
353 | ||
862fc625 | 354 | if (this->do_buttons) { |
e456d044 | 355 | // we don't want to do SPI in interrupt mode |
58d6d841 JM |
356 | this->do_buttons = false; |
357 | ||
358 | // read the actual buttons | |
862fc625 JM |
359 | int but = lcd->readButtons(); |
360 | if (but != 0) { | |
361 | this->idle_time = 0; | |
5ff3e912 JM |
362 | if(current_screen == NULL) { |
363 | // we were in startup screen so go to watch screen | |
364 | this->enter_screen(mms->watch_screen); | |
365 | return; | |
366 | } | |
f140cf3a JM |
367 | } |
368 | ||
58d6d841 | 369 | // fire events if the buttons are active and debounce is satisfied |
862fc625 JM |
370 | this->up_button.check_signal(but & BUTTON_UP); |
371 | this->down_button.check_signal(but & BUTTON_DOWN); | |
372 | this->back_button.check_signal(but & BUTTON_LEFT); | |
373 | this->click_button.check_signal(but & BUTTON_SELECT); | |
374 | this->pause_button.check_signal(but & BUTTON_PAUSE); | |
375 | } | |
399cb110 | 376 | |
35089dc7 | 377 | // If we are in menu mode and the position has changed |
862fc625 | 378 | if ( this->mode == MENU_MODE && this->counter_change() ) { |
35089dc7 JM |
379 | this->menu_update(); |
380 | } | |
381 | ||
382 | // If we are in control mode | |
862fc625 | 383 | if ( this->mode == CONTROL_MODE && this->counter_change() ) { |
58d6d841 | 384 | this->control_value_update(); |
35089dc7 JM |
385 | } |
386 | ||
387 | // If we must refresh | |
862fc625 | 388 | if ( this->refresh_flag ) { |
35089dc7 | 389 | this->refresh_flag = false; |
862fc625 | 390 | if (this->current_screen != NULL) { |
f140cf3a JM |
391 | this->current_screen->on_refresh(); |
392 | this->lcd->on_refresh(); | |
393 | } | |
35089dc7 JM |
394 | } |
395 | } | |
396 | ||
397 | // Hooks for button clicks | |
862fc625 JM |
398 | uint32_t Panel::on_up(uint32_t dummy) |
399 | { | |
25121d12 | 400 | // this is simulating encoder clicks, but needs to be inverted to |
b6e81799 JM |
401 | // increment values on up,increment by |
402 | int inc = (this->mode == CONTROL_MODE) ? 1 : -(this->menu_offset+1); | |
25121d12 | 403 | *this->counter += inc; |
58d6d841 JM |
404 | this->counter_changed = true; |
405 | return 0; | |
35089dc7 | 406 | } |
5ff3e912 | 407 | |
862fc625 JM |
408 | uint32_t Panel::on_down(uint32_t dummy) |
409 | { | |
b6e81799 | 410 | int inc = (this->mode == CONTROL_MODE) ? -1 : (this->menu_offset+1); |
25121d12 | 411 | *this->counter += inc; |
58d6d841 JM |
412 | this->counter_changed = true; |
413 | return 0; | |
35089dc7 JM |
414 | } |
415 | ||
416 | // on most menu screens will go back to previous higher menu | |
862fc625 JM |
417 | uint32_t Panel::on_back(uint32_t dummy) |
418 | { | |
419 | if (this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) { | |
58d6d841 JM |
420 | this->enter_screen(this->current_screen->parent); |
421 | } | |
422 | return 0; | |
35089dc7 JM |
423 | } |
424 | ||
862fc625 JM |
425 | uint32_t Panel::on_select(uint32_t dummy) |
426 | { | |
58d6d841 JM |
427 | // TODO make configurable, including turning off |
428 | // buzz is ignored on panels that do not support buzz | |
58d6d841 | 429 | this->click_changed = true; |
862fc625 JM |
430 | this->idle_time = 0; |
431 | lcd->buzz(60, 300); // 50ms 300Hz | |
58d6d841 | 432 | return 0; |
35089dc7 JM |
433 | } |
434 | ||
862fc625 JM |
435 | uint32_t Panel::on_pause(uint32_t dummy) |
436 | { | |
20e69746 | 437 | if (!THEKERNEL->pauser->paused()) { |
52500e58 | 438 | THEKERNEL->pauser->take(); |
862fc625 | 439 | } else { |
52500e58 | 440 | THEKERNEL->pauser->release(); |
52500e58 L |
441 | } |
442 | return 0; | |
443 | } | |
444 | ||
862fc625 JM |
445 | bool Panel::counter_change() |
446 | { | |
447 | if ( this->counter_changed ) { | |
448 | this->counter_changed = false; | |
449 | return true; | |
450 | } else { | |
451 | return false; | |
452 | } | |
453 | } | |
454 | bool Panel::click() | |
455 | { | |
456 | if ( this->click_changed ) { | |
457 | this->click_changed = false; | |
458 | return true; | |
459 | } else { | |
460 | return false; | |
461 | } | |
462 | } | |
35089dc7 JM |
463 | |
464 | ||
465 | // Enter menu mode | |
5971142d | 466 | void Panel::enter_menu_mode(bool force) |
862fc625 | 467 | { |
35089dc7 JM |
468 | this->mode = MENU_MODE; |
469 | this->counter = &this->menu_selected_line; | |
5971142d | 470 | this->menu_changed = force; |
35089dc7 JM |
471 | } |
472 | ||
862fc625 JM |
473 | void Panel::setup_menu(uint16_t rows) |
474 | { | |
f65ce58f JM |
475 | this->setup_menu(rows, min(rows, this->max_screen_lines())); |
476 | } | |
477 | ||
862fc625 JM |
478 | void Panel::setup_menu(uint16_t rows, uint16_t lines) |
479 | { | |
35089dc7 | 480 | this->menu_selected_line = 0; |
862fc625 | 481 | this->menu_current_line = 0; |
399cb110 | 482 | this->menu_start_line = 0; |
35089dc7 | 483 | this->menu_rows = rows; |
206167cf | 484 | this->panel_lines = lines; |
35089dc7 JM |
485 | } |
486 | ||
862fc625 JM |
487 | void Panel::menu_update() |
488 | { | |
399cb110 | 489 | // Limits, up and down |
446deda2 | 490 | // NOTE menu_selected_line is changed in an interrupt and can change at any time |
862fc625 | 491 | int msl = this->menu_selected_line; // hopefully this is atomic |
206167cf JM |
492 | |
493 | #if 0 | |
494 | // this allows it to wrap but with new method we dont; want to wrap | |
862fc625 JM |
495 | msl = msl % ( this->menu_rows << this->menu_offset ); |
496 | while ( msl < 0 ) { | |
497 | msl += this->menu_rows << this->menu_offset; | |
498 | } | |
206167cf JM |
499 | #else |
500 | // limit selected line to screen lines available | |
501 | if(msl >= this->menu_rows<<this->menu_offset){ | |
502 | msl= (this->menu_rows-1)<<this->menu_offset; | |
503 | }else if(msl < 0) msl= 0; | |
504 | #endif | |
446deda2 | 505 | |
206167cf | 506 | this->menu_selected_line = msl; // update atomically we hope |
b6e81799 JM |
507 | // figure out which actual line to select, if we have a menu offset it means we want to move one line per two clicks |
508 | if(msl % (this->menu_offset+1) == 0) { // only if divisible by offset | |
509 | this->menu_current_line = msl >> this->menu_offset; | |
510 | } | |
35089dc7 JM |
511 | |
512 | // What to display | |
206167cf JM |
513 | if ( this->menu_rows > this->panel_lines ) { |
514 | #if 0 | |
515 | // old way of scrolling not nice.... | |
862fc625 | 516 | if ( this->menu_current_line >= 2 ) { |
446deda2 | 517 | this->menu_start_line = this->menu_current_line - 1; |
35089dc7 | 518 | } |
206167cf JM |
519 | if ( this->menu_current_line > this->menu_rows - this->panel_lines ) { |
520 | this->menu_start_line = this->menu_rows - this->panel_lines; | |
521 | } | |
522 | #else | |
523 | // new way we only scroll the lines when the cursor hits the bottom of the screen or the top of the screen | |
524 | // do we want to scroll up? | |
525 | int sl= this->menu_current_line - this->menu_start_line; // screen line we are on | |
526 | if(sl >= this->panel_lines) { | |
527 | this->menu_start_line += ((sl+1)-this->panel_lines); // scroll up to keep it on the screen | |
528 | ||
529 | }else if(sl < 0 ) { // do we want to scroll down? | |
530 | this->menu_start_line += sl; // scroll down | |
35089dc7 | 531 | } |
206167cf JM |
532 | #endif |
533 | ||
534 | }else{ | |
535 | this->menu_start_line = 0; | |
35089dc7 JM |
536 | } |
537 | ||
538 | this->menu_changed = true; | |
539 | } | |
540 | ||
862fc625 JM |
541 | bool Panel::menu_change() |
542 | { | |
543 | if ( this->menu_changed ) { | |
544 | this->menu_changed = false; | |
545 | return true; | |
546 | } else { | |
547 | return false; | |
548 | } | |
35089dc7 JM |
549 | } |
550 | ||
862fc625 JM |
551 | bool Panel::control_value_change() |
552 | { | |
553 | if ( this->control_value_changed ) { | |
554 | this->control_value_changed = false; | |
555 | return true; | |
556 | } else { | |
557 | return false; | |
558 | } | |
35089dc7 JM |
559 | } |
560 | ||
1ad23cd3 | 561 | bool Panel::enter_control_mode(float passed_normal_increment, float passed_pressed_increment) |
862fc625 | 562 | { |
35089dc7 JM |
563 | this->mode = CONTROL_MODE; |
564 | this->normal_increment = passed_normal_increment; | |
35089dc7 JM |
565 | this->counter = &this->control_normal_counter; |
566 | this->control_normal_counter = 0; | |
58d6d841 JM |
567 | this->control_base_value = 0; |
568 | return true; | |
35089dc7 JM |
569 | } |
570 | ||
862fc625 JM |
571 | void Panel::control_value_update() |
572 | { | |
58d6d841 JM |
573 | // TODO what do we do here? |
574 | this->control_value_changed = true; | |
35089dc7 JM |
575 | } |
576 | ||
1ad23cd3 | 577 | void Panel::set_control_value(float value) |
862fc625 | 578 | { |
58d6d841 | 579 | this->control_base_value = value; |
35089dc7 JM |
580 | } |
581 | ||
1ad23cd3 | 582 | float Panel::get_control_value() |
862fc625 JM |
583 | { |
584 | return this->control_base_value + (this->control_normal_counter * this->normal_increment); | |
35089dc7 JM |
585 | } |
586 | ||
862fc625 JM |
587 | bool Panel::is_playing() const |
588 | { | |
58d6d841 JM |
589 | void *returned_data; |
590 | ||
75e6428d | 591 | bool ok = PublicData::get_value( player_checksum, is_playing_checksum, &returned_data ); |
862fc625 JM |
592 | if (ok) { |
593 | bool b = *static_cast<bool *>(returned_data); | |
58d6d841 JM |
594 | return b; |
595 | } | |
596 | return false; | |
7aa9fef3 JM |
597 | } |
598 | ||
862fc625 JM |
599 | void Panel::set_playing_file(string f) |
600 | { | |
7aa9fef3 | 601 | // just copy the first 20 characters after the first / if there |
862fc625 JM |
602 | size_t n = f.find_last_of('/'); |
603 | if (n == string::npos) n = 0; | |
604 | strncpy(playing_file, f.substr(n + 1, 19).c_str(), sizeof(playing_file)); | |
605 | playing_file[sizeof(playing_file) - 1] = 0; | |
7aa9fef3 | 606 | } |
cee1bb2d JM |
607 | |
608 | static float getTargetTemperature(uint16_t heater_cs) | |
609 | { | |
610 | void *returned_data; | |
75e6428d | 611 | bool ok = PublicData::get_value( temperature_control_checksum, heater_cs, current_temperature_checksum, &returned_data ); |
cee1bb2d JM |
612 | |
613 | if (ok) { | |
614 | struct pad_temperature temp = *static_cast<struct pad_temperature *>(returned_data); | |
615 | return temp.target_temperature; | |
616 | } | |
617 | ||
618 | return 0.0F; | |
619 | } | |
620 | ||
621 | void Panel::setup_temperature_screen() | |
622 | { | |
623 | // setup temperature screen | |
383c9c1c | 624 | auto mvs= new ModifyValuesScreen(false); // does not delete itself on exit |
cee1bb2d JM |
625 | this->temperature_screen= mvs; |
626 | ||
627 | // enumerate heaters and add a menu item for each one | |
628 | vector<uint16_t> modules; | |
629 | THEKERNEL->config->get_module_list( &modules, temperature_control_checksum ); | |
630 | ||
91bce49a | 631 | int cnt= 0; |
cee1bb2d JM |
632 | for(auto i : modules) { |
633 | if (!THEKERNEL->config->value(temperature_control_checksum, i, enable_checksum )->as_bool()) continue; | |
634 | void *returned_data; | |
75e6428d | 635 | bool ok = PublicData::get_value( temperature_control_checksum, i, current_temperature_checksum, &returned_data ); |
cee1bb2d JM |
636 | if (!ok) continue; |
637 | ||
c77d6dae | 638 | this->temperature_modules.push_back(i); |
cee1bb2d JM |
639 | struct pad_temperature t = *static_cast<struct pad_temperature *>(returned_data); |
640 | ||
641 | // rename if two of the known types | |
642 | const char *name; | |
643 | if(t.designator == "T") name= "Hotend"; | |
644 | else if(t.designator == "B") name= "Bed"; | |
645 | else name= t.designator.c_str(); | |
646 | ||
647 | mvs->addMenuItem(name, // menu name | |
648 | [i]() -> float { return getTargetTemperature(i); }, // getter | |
75e6428d | 649 | [i](float t) { PublicData::set_value( temperature_control_checksum, i, &t ); }, // setter |
cee1bb2d JM |
650 | 1.0F, // increment |
651 | 0.0F, // Min | |
652 | 500.0F // Max | |
91bce49a JM |
653 | ); |
654 | cnt++; | |
655 | } | |
656 | ||
657 | if(cnt== 0) { | |
658 | // no heaters and probably no extruders either | |
659 | delete mvs; | |
660 | this->temperature_screen= nullptr; | |
cee1bb2d JM |
661 | } |
662 | } | |
fb877329 JM |
663 | |
664 | bool Panel::mount_external_sd(bool on) | |
665 | { | |
666 | // now setup the external sdcard if we have one and mount it | |
667 | if(on) { | |
668 | if(this->sd == nullptr) { | |
669 | PinName mosi, miso, sclk, cs= this->extsd_spi_cs; | |
670 | if(extsd_spi_channel == 0) { | |
671 | mosi = P0_18; miso = P0_17; sclk = P0_15; | |
672 | } else if(extsd_spi_channel == 1) { | |
673 | mosi = P0_9; miso = P0_8; sclk = P0_7; | |
674 | } else{ | |
675 | this->external_sd_enable= false; | |
676 | THEKERNEL->streams->printf("Bad SPI channel for external SDCard\n"); | |
677 | return false; | |
678 | } | |
679 | size_t n= sizeof(SDCard); | |
680 | void *v = AHB0.alloc(n); | |
681 | memset(v, 0, n); // clear the allocated memory | |
682 | this->sd= new(v) SDCard(mosi, miso, sclk, cs); // allocate object using zeroed memory | |
683 | } | |
684 | delete this->extmounter; // if it was not unmounted before | |
685 | size_t n= sizeof(SDFAT); | |
686 | void *v = AHB0.alloc(n); | |
687 | memset(v, 0, n); // clear the allocated memory | |
688 | this->extmounter= new(v) SDFAT("ext", this->sd); // use cleared allocated memory | |
689 | this->sd->disk_initialize(); // first one seems to fail, but works next time | |
690 | THEKERNEL->streams->printf("External SDcard mounted as /ext\n"); | |
691 | }else{ | |
692 | delete this->extmounter; | |
693 | this->extmounter= nullptr; | |
694 | THEKERNEL->streams->printf("External SDcard unmounted\n"); | |
695 | } | |
696 | return true; | |
697 | } | |
698 | ||
699 | void Panel::on_second_tick(void *arg) | |
700 | { | |
892508b8 | 701 | if(!this->external_sd_enable || this->start_up) return; |
fb877329 JM |
702 | |
703 | // sd insert detect, mount sdcard if inserted, unmount if removed | |
704 | if(this->sdcd_pin.connected()) { | |
705 | if(this->extmounter == nullptr && this->sdcd_pin.get()) { | |
706 | mount_external_sd(true); | |
707 | // go to the play screen and the /ext directory | |
892508b8 | 708 | // TODO we don't want to do this if we just booted and card was already in |
fb877329 JM |
709 | THEKERNEL->current_path= "/ext"; |
710 | MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen); | |
711 | THEPANEL->enter_screen(mms->file_screen); | |
712 | ||
713 | }else if(this->extmounter != nullptr && !this->sdcd_pin.get()){ | |
714 | mount_external_sd(false); | |
715 | } | |
716 | }else{ | |
717 | // TODO for panels with no sd card detect we need to poll to see if card is inserted - or not | |
718 | } | |
719 | } | |
76217df5 JM |
720 | |
721 | void Panel::on_halt(void *arg) | |
722 | { | |
723 | halted= true; | |
724 | } |