Add halt status to panel and a way to clear it
[clinton/Smoothieware.git] / src / modules / utils / panel / Panel.cpp
CommitLineData
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
67Panel* Panel::instance= nullptr;
68
862fc625
JM
69Panel::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
89Panel::~Panel()
90{
58d6d841 91 delete this->lcd;
fb877329
JM
92 delete this->extmounter;
93 delete this->sd;
35089dc7
JM
94}
95
862fc625
JM
96void 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
200void 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
211void 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
219uint32_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
227uint32_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
251uint32_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
258uint32_t Panel::encoder_tick(uint32_t dummy)
259{
260 this->do_encoder = true;
261 return 0;
262}
263
862fc625 264void 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
279void 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 290static 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
305void 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
398uint32_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
408uint32_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
417uint32_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
425uint32_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
435uint32_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
445bool Panel::counter_change()
446{
447 if ( this->counter_changed ) {
448 this->counter_changed = false;
449 return true;
450 } else {
451 return false;
452 }
453}
454bool 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 466void 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
473void Panel::setup_menu(uint16_t rows)
474{
f65ce58f
JM
475 this->setup_menu(rows, min(rows, this->max_screen_lines()));
476}
477
862fc625
JM
478void 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
487void 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
541bool 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
551bool 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 561bool 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
571void 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 577void Panel::set_control_value(float value)
862fc625 578{
58d6d841 579 this->control_base_value = value;
35089dc7
JM
580}
581
1ad23cd3 582float 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
587bool 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
599void 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
608static 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
621void 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
664bool 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
699void 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
721void Panel::on_halt(void *arg)
722{
723 halted= true;
724}