refactor ON_HALT, add THEKERNEL->is_halted() for modules that just need to test 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"
02e4b295 39#include "TemperatureControlPool.h"
35089dc7 40
fb877329
JM
41// for parse_pins in mbed
42#include "pinmap.h"
43
ca6effd7
JM
44#define panel_checksum CHECKSUM("panel")
45#define enable_checksum CHECKSUM("enable")
46#define lcd_checksum CHECKSUM("lcd")
ca6effd7
JM
47#define rrd_glcd_checksum CHECKSUM("reprap_discount_glcd")
48#define st7565_glcd_checksum CHECKSUM("st7565_glcd")
8b6bc932
JM
49#define viki2_checksum CHECKSUM("viki2")
50#define mini_viki2_checksum CHECKSUM("mini_viki2")
3ae0e673 51#define universal_adapter_checksum CHECKSUM("universal_adapter")
ca6effd7 52
fa3f7b30
JM
53#define menu_offset_checksum CHECKSUM("menu_offset")
54#define encoder_resolution_checksum CHECKSUM("encoder_resolution")
55#define jog_x_feedrate_checksum CHECKSUM("alpha_jog_feedrate")
56#define jog_y_feedrate_checksum CHECKSUM("beta_jog_feedrate")
57#define jog_z_feedrate_checksum CHECKSUM("gamma_jog_feedrate")
f1e696a3 58#define longpress_delay_checksum CHECKSUM("longpress_delay")
ca6effd7 59
fb877329
JM
60#define ext_sd_checksum CHECKSUM("external_sd")
61#define sdcd_pin_checksum CHECKSUM("sdcd_pin")
62#define spi_channel_checksum CHECKSUM("spi_channel")
63#define spi_cs_pin_checksum CHECKSUM("spi_cs_pin")
64
ca6effd7
JM
65#define hotend_temp_checksum CHECKSUM("hotend_temperature")
66#define bed_temp_checksum CHECKSUM("bed_temperature")
c89338bd 67#define panel_display_message_checksum CHECKSUM("display_message")
ca6effd7 68
cee1bb2d
JM
69Panel* Panel::instance= nullptr;
70
862fc625
JM
71Panel::Panel()
72{
cee1bb2d 73 instance= this;
35089dc7
JM
74 this->counter_changed = false;
75 this->click_changed = false;
76 this->refresh_flag = false;
77 this->enter_menu_mode();
862fc625 78 this->lcd = NULL;
58d6d841 79 this->do_buttons = false;
e456d044 80 this->do_encoder = false;
862fc625
JM
81 this->idle_time = 0;
82 this->start_up = true;
83 this->current_screen = NULL;
fb877329
JM
84 this->sd= nullptr;
85 this->extmounter= nullptr;
86 this->external_sd_enable= false;
7aa9fef3 87 strcpy(this->playing_file, "Playing file");
35089dc7
JM
88}
89
862fc625
JM
90Panel::~Panel()
91{
58d6d841 92 delete this->lcd;
fb877329
JM
93 delete this->extmounter;
94 delete this->sd;
35089dc7
JM
95}
96
862fc625
JM
97void Panel::on_module_loaded()
98{
35089dc7 99 // Exit if this module is not enabled
314ab8f7 100 if ( !THEKERNEL->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ) {
58d6d841
JM
101 delete this;
102 return;
399cb110 103 }
35089dc7 104
58d6d841
JM
105 // Initialise the LCD, see which LCD to use
106 if (this->lcd != NULL) delete this->lcd;
7713b1ef 107 int lcd_cksm = get_checksum(THEKERNEL->config->value(panel_checksum, lcd_checksum)->by_default("reprap_discount_glcd")->as_string());
58d6d841
JM
108
109 // Note checksums are not const expressions when in debug mode, so don't use switch
7713b1ef 110 if (lcd_cksm == rrd_glcd_checksum) {
357d0eda 111 this->lcd = new ReprapDiscountGLCD();
862fc625 112 } else if (lcd_cksm == st7565_glcd_checksum) {
84e7fd5a 113 this->lcd = new ST7565();
8b6bc932 114 } else if (lcd_cksm == viki2_checksum) {
8ed554f3 115 this->lcd = new ST7565(1); // variant 1
8b6bc932 116 } else if (lcd_cksm == mini_viki2_checksum) {
8ed554f3 117 this->lcd = new ST7565(2); // variant 2
3ae0e673
JM
118 } else if (lcd_cksm == universal_adapter_checksum) {
119 this->lcd = new UniversalAdapter();
862fc625 120 } else {
7713b1ef
JM
121 // no known lcd type defined
122 delete this;
58d6d841
JM
123 return;
124 }
125
fb877329
JM
126 // external sd
127 if(THEKERNEL->config->value( panel_checksum, ext_sd_checksum )->by_default(false)->as_bool()) {
128 this->external_sd_enable= true;
129 // external sdcard detect
130 this->sdcd_pin.from_string(THEKERNEL->config->value( panel_checksum, ext_sd_checksum, sdcd_pin_checksum )->by_default("nc")->as_string())->as_input();
131 this->extsd_spi_channel = THEKERNEL->config->value(panel_checksum, ext_sd_checksum, spi_channel_checksum)->by_default(0)->as_number();
132 string s= THEKERNEL->config->value( panel_checksum, ext_sd_checksum, spi_cs_pin_checksum)->by_default("2.8")->as_string();
133 s= "P" + s; // Pinnames need to be Px_x
134 this->extsd_spi_cs= parse_pins(s.c_str());
135 this->register_for_event(ON_SECOND_TICK);
136 }
137
cee1bb2d
JM
138 // these need to be called here as they need the config cache loaded as they enumerate modules
139 this->custom_screen= new CustomScreen();
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);
c89338bd 192 this->register_for_event(ON_SET_PUBLIC_DATA);
767e7c51 193
35089dc7 194 // Refresh timer
314ab8f7 195 THEKERNEL->slow_ticker->attach( 20, this, &Panel::refresh_tick );
35089dc7
JM
196}
197
198// Enter a screen, we only care about it now
862fc625
JM
199void Panel::enter_screen(PanelScreen *screen)
200{
383c9c1c
JM
201 if(this->current_screen != nullptr)
202 this->current_screen->on_exit();
203
35089dc7
JM
204 this->current_screen = screen;
205 this->reset_counter();
206 this->current_screen->on_enter();
207}
208
209// Reset the counter
862fc625
JM
210void Panel::reset_counter()
211{
35089dc7
JM
212 *this->counter = 0;
213 this->counter_changed = false;
214}
215
216// Indicate the idle loop we want to call the refresh hook in the current screen
5ff3e912 217// called 20 times a second
862fc625
JM
218uint32_t Panel::refresh_tick(uint32_t dummy)
219{
58d6d841
JM
220 this->refresh_flag = true;
221 this->idle_time++;
222 return 0;
35089dc7
JM
223}
224
e93caece 225// Encoder pins changed in interrupt or call from on_idle
862fc625
JM
226uint32_t Panel::encoder_check(uint32_t dummy)
227{
e456d044
JM
228 // if encoder reads go through SPI like on universal panel adapter this needs to be
229 // done in idle loop, this is indicated by lcd->encoderReturnsDelta()
230 // however when reading encoder directly it needs to be done
231 // frequently, universal panel adapter will return an actual delta count so won't miss any if polled slowly
e93caece 232 // 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
233 static int encoder_counter = 0; // keeps track of absolute encoder position
234 static int last_encoder_click= 0; // last readfing of divided encoder count
35089dc7 235 int change = lcd->readEncoderDelta();
58d6d841 236 encoder_counter += change;
10d11dfd
JM
237 int clicks= encoder_counter/this->encoder_click_resolution;
238 int delta= clicks - last_encoder_click; // the number of clicks this time
239 last_encoder_click= clicks;
b6e81799 240
10d11dfd 241 if ( delta != 0 ) {
58d6d841 242 this->counter_changed = true;
10d11dfd 243 (*this->counter) += delta;
862fc625 244 this->idle_time = 0;
35089dc7 245 }
58d6d841 246 return 0;
35089dc7
JM
247}
248
249// Read and update each button
862fc625
JM
250uint32_t Panel::button_tick(uint32_t dummy)
251{
58d6d841
JM
252 this->do_buttons = true;
253 return 0;
35089dc7
JM
254}
255
e456d044
JM
256// Read and update encoder
257uint32_t Panel::encoder_tick(uint32_t dummy)
258{
259 this->do_encoder = true;
260 return 0;
261}
262
c89338bd 263void Panel::on_set_public_data(void *argument)
399cb110 264{
c89338bd
JM
265 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
266
267 if(!pdr->starts_with(panel_checksum)) return;
268
269 if(!pdr->second_element_is(panel_display_message_checksum)) return;
270
271 string *s = static_cast<string *>(pdr->get_data_ptr());
272 if (s->size() > 20) {
273 this->message = s->substr(0, 20);
274 } else {
275 this->message= *s;
399cb110
JM
276 }
277}
278
35089dc7 279// on main loop, we can send gcodes or do anything that waits in this loop
862fc625
JM
280void Panel::on_main_loop(void *argument)
281{
282 if (this->current_screen != NULL) {
f140cf3a
JM
283 this->current_screen->on_main_loop();
284 this->lcd->on_main_loop();
285 }
35089dc7
JM
286}
287
c96fd70a
JM
288
289#define ohw_logo_antipixel_width 80
290#define ohw_logo_antipixel_height 15
161164be 291static const uint8_t ohw_logo_antipixel_bits[] = {
862fc625
JM
292 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
293 0x00, 0x00, 0x00, 0x01, 0x80, 0x0C, 0x00, 0x33, 0x18, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x5E,
294 0x80, 0x2D, 0x6B, 0x9B, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x18, 0xAB, 0xFF, 0xFF,
295 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x7B, 0xB3, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x7F, 0x80, 0x33,
296 0x78, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x81, 0xF3, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD,
297 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xB3, 0x18, 0xDD, 0x98, 0xC5, 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xAD,
298 0x6B, 0x5D, 0x6B, 0x5D, 0x80, 0x73, 0x80, 0x3F, 0xFC, 0x21, 0x1B, 0x55, 0x08, 0xC5, 0x80, 0xF3,
299 0xC0, 0x3F, 0xFD, 0xAD, 0x5B, 0x49, 0x6A, 0xDD, 0x80, 0xE1, 0xC0, 0x3F, 0xFD, 0xAD, 0x68, 0xDD,
300 0x6B, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
301 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
c96fd70a
JM
302};
303
35089dc7
JM
304// On idle things, we don't want to do shit in interrupts
305// don't queue gcodes in this
862fc625
JM
306void Panel::on_idle(void *argument)
307{
308 if (this->start_up) {
f140cf3a
JM
309 this->lcd->init();
310
ebc64506
JM
311 Version v;
312 string build(v.get_build());
313 string date(v.get_build_date());
314 this->lcd->clear();
862fc625
JM
315 this->lcd->setCursor(0, 0); this->lcd->printf("Welcome to Smoothie");
316 this->lcd->setCursor(0, 1); this->lcd->printf("%s", build.substr(0, 20).c_str());
317 this->lcd->setCursor(0, 2); this->lcd->printf("%s", date.substr(0, 20).c_str());
318 this->lcd->setCursor(0, 3); this->lcd->printf("Please wait....");
ebc64506 319
862fc625 320 if (this->lcd->hasGraphics()) {
c96fd70a
JM
321 this->lcd->bltGlyph(24, 40, ohw_logo_antipixel_width, ohw_logo_antipixel_height, ohw_logo_antipixel_bits);
322 }
323
f54d25cb
JM
324 this->lcd->on_refresh(true); // tell lcd to display now
325
f140cf3a 326 // Default top screen
ca6effd7 327 this->top_screen= new MainMenuScreen();
ca6effd7 328 this->custom_screen->set_parent(this->top_screen);
862fc625 329 this->start_up = false;
5ff3e912 330 return;
f140cf3a 331 }
35089dc7 332
ca6effd7 333 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
58d6d841 334 // after being idle for a while switch to Watch screen
5ff3e912 335 if (this->current_screen != NULL && this->idle_time > this->current_screen->idle_timeout_secs()*20) {
862fc625 336 this->idle_time = 0;
ca6effd7
JM
337 if (mms->watch_screen != this->current_screen) {
338 this->enter_screen(mms->watch_screen);
58d6d841
JM
339 // TODO do we need to reset any state?
340 }
399cb110 341
58d6d841
JM
342 return;
343 }
399cb110 344
5ff3e912
JM
345 if(current_screen == NULL && this->idle_time > 20*4) {
346 this->enter_screen(mms->watch_screen);
347 return;
348 }
349
e456d044
JM
350 if(this->do_encoder) {
351 this->do_encoder= false;
352 encoder_check(0);
353 }
354
862fc625 355 if (this->do_buttons) {
e456d044 356 // we don't want to do SPI in interrupt mode
58d6d841
JM
357 this->do_buttons = false;
358
359 // read the actual buttons
862fc625
JM
360 int but = lcd->readButtons();
361 if (but != 0) {
362 this->idle_time = 0;
5ff3e912
JM
363 if(current_screen == NULL) {
364 // we were in startup screen so go to watch screen
365 this->enter_screen(mms->watch_screen);
366 return;
367 }
f140cf3a
JM
368 }
369
58d6d841 370 // fire events if the buttons are active and debounce is satisfied
862fc625
JM
371 this->up_button.check_signal(but & BUTTON_UP);
372 this->down_button.check_signal(but & BUTTON_DOWN);
373 this->back_button.check_signal(but & BUTTON_LEFT);
374 this->click_button.check_signal(but & BUTTON_SELECT);
375 this->pause_button.check_signal(but & BUTTON_PAUSE);
376 }
399cb110 377
35089dc7 378 // If we are in menu mode and the position has changed
862fc625 379 if ( this->mode == MENU_MODE && this->counter_change() ) {
35089dc7
JM
380 this->menu_update();
381 }
382
383 // If we are in control mode
862fc625 384 if ( this->mode == CONTROL_MODE && this->counter_change() ) {
58d6d841 385 this->control_value_update();
35089dc7
JM
386 }
387
388 // If we must refresh
862fc625 389 if ( this->refresh_flag ) {
35089dc7 390 this->refresh_flag = false;
862fc625 391 if (this->current_screen != NULL) {
f140cf3a
JM
392 this->current_screen->on_refresh();
393 this->lcd->on_refresh();
394 }
35089dc7
JM
395 }
396}
397
398// Hooks for button clicks
862fc625
JM
399uint32_t Panel::on_up(uint32_t dummy)
400{
25121d12 401 // this is simulating encoder clicks, but needs to be inverted to
b6e81799
JM
402 // increment values on up,increment by
403 int inc = (this->mode == CONTROL_MODE) ? 1 : -(this->menu_offset+1);
25121d12 404 *this->counter += inc;
58d6d841
JM
405 this->counter_changed = true;
406 return 0;
35089dc7 407}
5ff3e912 408
862fc625
JM
409uint32_t Panel::on_down(uint32_t dummy)
410{
b6e81799 411 int inc = (this->mode == CONTROL_MODE) ? -1 : (this->menu_offset+1);
25121d12 412 *this->counter += inc;
58d6d841
JM
413 this->counter_changed = true;
414 return 0;
35089dc7
JM
415}
416
417// on most menu screens will go back to previous higher menu
862fc625
JM
418uint32_t Panel::on_back(uint32_t dummy)
419{
420 if (this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) {
58d6d841
JM
421 this->enter_screen(this->current_screen->parent);
422 }
423 return 0;
35089dc7
JM
424}
425
862fc625
JM
426uint32_t Panel::on_select(uint32_t dummy)
427{
58d6d841
JM
428 // TODO make configurable, including turning off
429 // buzz is ignored on panels that do not support buzz
58d6d841 430 this->click_changed = true;
862fc625
JM
431 this->idle_time = 0;
432 lcd->buzz(60, 300); // 50ms 300Hz
58d6d841 433 return 0;
35089dc7
JM
434}
435
862fc625
JM
436uint32_t Panel::on_pause(uint32_t dummy)
437{
20e69746 438 if (!THEKERNEL->pauser->paused()) {
52500e58 439 THEKERNEL->pauser->take();
862fc625 440 } else {
52500e58 441 THEKERNEL->pauser->release();
52500e58
L
442 }
443 return 0;
444}
445
862fc625
JM
446bool Panel::counter_change()
447{
448 if ( this->counter_changed ) {
449 this->counter_changed = false;
450 return true;
451 } else {
452 return false;
453 }
454}
455bool Panel::click()
456{
457 if ( this->click_changed ) {
458 this->click_changed = false;
459 return true;
460 } else {
461 return false;
462 }
463}
35089dc7
JM
464
465
466// Enter menu mode
5971142d 467void Panel::enter_menu_mode(bool force)
862fc625 468{
35089dc7
JM
469 this->mode = MENU_MODE;
470 this->counter = &this->menu_selected_line;
5971142d 471 this->menu_changed = force;
35089dc7
JM
472}
473
862fc625
JM
474void Panel::setup_menu(uint16_t rows)
475{
f65ce58f
JM
476 this->setup_menu(rows, min(rows, this->max_screen_lines()));
477}
478
862fc625
JM
479void Panel::setup_menu(uint16_t rows, uint16_t lines)
480{
35089dc7 481 this->menu_selected_line = 0;
862fc625 482 this->menu_current_line = 0;
399cb110 483 this->menu_start_line = 0;
35089dc7 484 this->menu_rows = rows;
206167cf 485 this->panel_lines = lines;
35089dc7
JM
486}
487
862fc625
JM
488void Panel::menu_update()
489{
399cb110 490 // Limits, up and down
446deda2 491 // NOTE menu_selected_line is changed in an interrupt and can change at any time
862fc625 492 int msl = this->menu_selected_line; // hopefully this is atomic
206167cf
JM
493
494 #if 0
495 // this allows it to wrap but with new method we dont; want to wrap
862fc625
JM
496 msl = msl % ( this->menu_rows << this->menu_offset );
497 while ( msl < 0 ) {
498 msl += this->menu_rows << this->menu_offset;
499 }
206167cf
JM
500 #else
501 // limit selected line to screen lines available
502 if(msl >= this->menu_rows<<this->menu_offset){
503 msl= (this->menu_rows-1)<<this->menu_offset;
504 }else if(msl < 0) msl= 0;
505 #endif
446deda2 506
206167cf 507 this->menu_selected_line = msl; // update atomically we hope
b6e81799
JM
508 // figure out which actual line to select, if we have a menu offset it means we want to move one line per two clicks
509 if(msl % (this->menu_offset+1) == 0) { // only if divisible by offset
510 this->menu_current_line = msl >> this->menu_offset;
511 }
35089dc7
JM
512
513 // What to display
206167cf
JM
514 if ( this->menu_rows > this->panel_lines ) {
515 #if 0
516 // old way of scrolling not nice....
862fc625 517 if ( this->menu_current_line >= 2 ) {
446deda2 518 this->menu_start_line = this->menu_current_line - 1;
35089dc7 519 }
206167cf
JM
520 if ( this->menu_current_line > this->menu_rows - this->panel_lines ) {
521 this->menu_start_line = this->menu_rows - this->panel_lines;
522 }
523 #else
524 // new way we only scroll the lines when the cursor hits the bottom of the screen or the top of the screen
525 // do we want to scroll up?
526 int sl= this->menu_current_line - this->menu_start_line; // screen line we are on
527 if(sl >= this->panel_lines) {
528 this->menu_start_line += ((sl+1)-this->panel_lines); // scroll up to keep it on the screen
529
530 }else if(sl < 0 ) { // do we want to scroll down?
531 this->menu_start_line += sl; // scroll down
35089dc7 532 }
206167cf
JM
533 #endif
534
535 }else{
536 this->menu_start_line = 0;
35089dc7
JM
537 }
538
539 this->menu_changed = true;
540}
541
862fc625
JM
542bool Panel::menu_change()
543{
544 if ( this->menu_changed ) {
545 this->menu_changed = false;
546 return true;
547 } else {
548 return false;
549 }
35089dc7
JM
550}
551
862fc625
JM
552bool Panel::control_value_change()
553{
554 if ( this->control_value_changed ) {
555 this->control_value_changed = false;
556 return true;
557 } else {
558 return false;
559 }
35089dc7
JM
560}
561
1ad23cd3 562bool Panel::enter_control_mode(float passed_normal_increment, float passed_pressed_increment)
862fc625 563{
35089dc7
JM
564 this->mode = CONTROL_MODE;
565 this->normal_increment = passed_normal_increment;
35089dc7
JM
566 this->counter = &this->control_normal_counter;
567 this->control_normal_counter = 0;
58d6d841
JM
568 this->control_base_value = 0;
569 return true;
35089dc7
JM
570}
571
862fc625
JM
572void Panel::control_value_update()
573{
58d6d841
JM
574 // TODO what do we do here?
575 this->control_value_changed = true;
35089dc7
JM
576}
577
1ad23cd3 578void Panel::set_control_value(float value)
862fc625 579{
58d6d841 580 this->control_base_value = value;
35089dc7
JM
581}
582
1ad23cd3 583float Panel::get_control_value()
862fc625
JM
584{
585 return this->control_base_value + (this->control_normal_counter * this->normal_increment);
35089dc7
JM
586}
587
862fc625
JM
588bool Panel::is_playing() const
589{
58d6d841
JM
590 void *returned_data;
591
75e6428d 592 bool ok = PublicData::get_value( player_checksum, is_playing_checksum, &returned_data );
862fc625
JM
593 if (ok) {
594 bool b = *static_cast<bool *>(returned_data);
58d6d841
JM
595 return b;
596 }
597 return false;
7aa9fef3
JM
598}
599
5f1a896b
JM
600bool Panel::is_suspended() const
601{
602 void *returned_data;
603
604 bool ok = PublicData::get_value( player_checksum, is_suspended_checksum, &returned_data );
605 if (ok) {
606 bool b = *static_cast<bool *>(returned_data);
607 return b;
608 }
609 return false;
610}
611
862fc625
JM
612void Panel::set_playing_file(string f)
613{
7aa9fef3 614 // just copy the first 20 characters after the first / if there
862fc625
JM
615 size_t n = f.find_last_of('/');
616 if (n == string::npos) n = 0;
617 strncpy(playing_file, f.substr(n + 1, 19).c_str(), sizeof(playing_file));
618 playing_file[sizeof(playing_file) - 1] = 0;
7aa9fef3 619}
cee1bb2d 620
fb877329
JM
621bool Panel::mount_external_sd(bool on)
622{
623 // now setup the external sdcard if we have one and mount it
624 if(on) {
625 if(this->sd == nullptr) {
626 PinName mosi, miso, sclk, cs= this->extsd_spi_cs;
627 if(extsd_spi_channel == 0) {
628 mosi = P0_18; miso = P0_17; sclk = P0_15;
629 } else if(extsd_spi_channel == 1) {
630 mosi = P0_9; miso = P0_8; sclk = P0_7;
631 } else{
632 this->external_sd_enable= false;
633 THEKERNEL->streams->printf("Bad SPI channel for external SDCard\n");
634 return false;
635 }
636 size_t n= sizeof(SDCard);
637 void *v = AHB0.alloc(n);
638 memset(v, 0, n); // clear the allocated memory
639 this->sd= new(v) SDCard(mosi, miso, sclk, cs); // allocate object using zeroed memory
640 }
641 delete this->extmounter; // if it was not unmounted before
642 size_t n= sizeof(SDFAT);
643 void *v = AHB0.alloc(n);
644 memset(v, 0, n); // clear the allocated memory
645 this->extmounter= new(v) SDFAT("ext", this->sd); // use cleared allocated memory
646 this->sd->disk_initialize(); // first one seems to fail, but works next time
647 THEKERNEL->streams->printf("External SDcard mounted as /ext\n");
648 }else{
649 delete this->extmounter;
650 this->extmounter= nullptr;
651 THEKERNEL->streams->printf("External SDcard unmounted\n");
652 }
653 return true;
654}
655
656void Panel::on_second_tick(void *arg)
657{
892508b8 658 if(!this->external_sd_enable || this->start_up) return;
fb877329
JM
659
660 // sd insert detect, mount sdcard if inserted, unmount if removed
661 if(this->sdcd_pin.connected()) {
662 if(this->extmounter == nullptr && this->sdcd_pin.get()) {
663 mount_external_sd(true);
664 // go to the play screen and the /ext directory
892508b8 665 // TODO we don't want to do this if we just booted and card was already in
fb877329
JM
666 THEKERNEL->current_path= "/ext";
667 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
668 THEPANEL->enter_screen(mms->file_screen);
669
670 }else if(this->extmounter != nullptr && !this->sdcd_pin.get()){
671 mount_external_sd(false);
672 }
673 }else{
674 // TODO for panels with no sd card detect we need to poll to see if card is inserted - or not
675 }
676}