Merge remote-tracking branch 'upstream/edge' into upstream-master
[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"
cee1bb2d
JM
23#include "TemperatureControlPublicAccess.h"
24#include "ModifyValuesScreen.h"
25#include "PublicDataRequest.h"
61134a65 26#include "PublicData.h"
fb877329
JM
27#include "StreamOutputPool.h"
28#include "platform_memory.h"
35089dc7 29
357d0eda 30#include "panels/ReprapDiscountGLCD.h"
84e7fd5a 31#include "panels/ST7565.h"
3ae0e673
JM
32#include "panels/UniversalAdapter.h"
33
f140cf3a 34#include "version.h"
7af0714f 35#include "checksumm.h"
8d54c34c 36#include "ConfigValue.h"
7713b1ef 37#include "Config.h"
02e4b295 38#include "TemperatureControlPool.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")
c89338bd 66#define panel_display_message_checksum CHECKSUM("display_message")
ca6effd7 67
cee1bb2d
JM
68Panel* Panel::instance= nullptr;
69
862fc625
JM
70Panel::Panel()
71{
cee1bb2d 72 instance= this;
35089dc7
JM
73 this->counter_changed = false;
74 this->click_changed = false;
75 this->refresh_flag = false;
76 this->enter_menu_mode();
862fc625 77 this->lcd = NULL;
58d6d841 78 this->do_buttons = false;
e456d044 79 this->do_encoder = false;
862fc625
JM
80 this->idle_time = 0;
81 this->start_up = true;
82 this->current_screen = NULL;
fb877329
JM
83 this->sd= nullptr;
84 this->extmounter= nullptr;
85 this->external_sd_enable= 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();
ca6effd7 139
58d6d841
JM
140 // some panels may need access to this global info
141 this->lcd->setPanel(this);
142
f65ce58f 143 // the number of screen lines the panel supports
862fc625 144 this->screen_lines = this->lcd->get_screen_lines();
399cb110 145
19fb4629
JM
146 // some encoders may need more clicks to move menu, this is a divisor and is in config as it is
147 // an end user usability issue
314ab8f7 148 this->menu_offset = THEKERNEL->config->value( panel_checksum, menu_offset_checksum )->by_default(0)->as_number();
19fb4629 149
fa3f7b30 150 // override default encoder resolution if needed
314ab8f7 151 this->encoder_click_resolution = THEKERNEL->config->value( panel_checksum, encoder_resolution_checksum )->by_default(this->lcd->getEncoderResolution())->as_number();
fa3f7b30 152
58d6d841 153 // load jogging feedrates in mm/min
1ad23cd3
MM
154 jogging_speed_mm_min[0] = THEKERNEL->config->value( panel_checksum, jog_x_feedrate_checksum )->by_default(3000.0f)->as_number();
155 jogging_speed_mm_min[1] = THEKERNEL->config->value( panel_checksum, jog_y_feedrate_checksum )->by_default(3000.0f)->as_number();
156 jogging_speed_mm_min[2] = THEKERNEL->config->value( panel_checksum, jog_z_feedrate_checksum )->by_default(300.0f )->as_number();
f19a841a
JM
157
158 // load the default preset temeratures
1ad23cd3
MM
159 default_hotend_temperature = THEKERNEL->config->value( panel_checksum, hotend_temp_checksum )->by_default(185.0f )->as_number();
160 default_bed_temperature = THEKERNEL->config->value( panel_checksum, bed_temp_checksum )->by_default(60.0f )->as_number();
f19a841a 161
399cb110 162
95195a29
JM
163 this->up_button.up_attach( this, &Panel::on_up );
164 this->down_button.up_attach( this, &Panel::on_down );
ab4abea9 165 this->click_button.up_attach( this, &Panel::on_select );
95195a29 166 this->back_button.up_attach( this, &Panel::on_back );
35089dc7 167
355c5796
L
168
169 //setting longpress_delay
170 int longpress_delay = THEKERNEL->config->value( panel_checksum, longpress_delay_checksum )->by_default(0)->as_number();
171 this->up_button.set_longpress_delay(longpress_delay);
f1e696a3 172 this->down_button.set_longpress_delay(longpress_delay);
355c5796
L
173// this->click_button.set_longpress_delay(longpress_delay);
174// this->back_button.set_longpress_delay(longpress_delay);
175// this->pause_button.set_longpress_delay(longpress_delay);
61134a65
JM
176
177
c206438f 178 THEKERNEL->slow_ticker->attach( 50, this, &Panel::button_tick );
e456d044
JM
179 if(lcd->encoderReturnsDelta()) {
180 // panel handles encoder pins and returns a delta
10d11dfd 181 THEKERNEL->slow_ticker->attach( 10, this, &Panel::encoder_tick );
e456d044
JM
182 }else{
183 // read encoder pins
184 THEKERNEL->slow_ticker->attach( 1000, this, &Panel::encoder_check );
185 }
399cb110 186
767e7c51
JM
187 // Register for events
188 this->register_for_event(ON_IDLE);
189 this->register_for_event(ON_MAIN_LOOP);
c89338bd 190 this->register_for_event(ON_SET_PUBLIC_DATA);
767e7c51 191
35089dc7 192 // Refresh timer
314ab8f7 193 THEKERNEL->slow_ticker->attach( 20, this, &Panel::refresh_tick );
35089dc7
JM
194}
195
196// Enter a screen, we only care about it now
862fc625
JM
197void Panel::enter_screen(PanelScreen *screen)
198{
383c9c1c
JM
199 if(this->current_screen != nullptr)
200 this->current_screen->on_exit();
201
35089dc7
JM
202 this->current_screen = screen;
203 this->reset_counter();
204 this->current_screen->on_enter();
205}
206
207// Reset the counter
862fc625
JM
208void Panel::reset_counter()
209{
35089dc7
JM
210 *this->counter = 0;
211 this->counter_changed = false;
212}
213
214// Indicate the idle loop we want to call the refresh hook in the current screen
5ff3e912 215// called 20 times a second
862fc625
JM
216uint32_t Panel::refresh_tick(uint32_t dummy)
217{
58d6d841
JM
218 this->refresh_flag = true;
219 this->idle_time++;
220 return 0;
35089dc7
JM
221}
222
e93caece 223// Encoder pins changed in interrupt or call from on_idle
862fc625
JM
224uint32_t Panel::encoder_check(uint32_t dummy)
225{
e456d044
JM
226 // if encoder reads go through SPI like on universal panel adapter this needs to be
227 // done in idle loop, this is indicated by lcd->encoderReturnsDelta()
228 // however when reading encoder directly it needs to be done
229 // frequently, universal panel adapter will return an actual delta count so won't miss any if polled slowly
e93caece 230 // 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
231 static int encoder_counter = 0; // keeps track of absolute encoder position
232 static int last_encoder_click= 0; // last readfing of divided encoder count
35089dc7 233 int change = lcd->readEncoderDelta();
58d6d841 234 encoder_counter += change;
10d11dfd
JM
235 int clicks= encoder_counter/this->encoder_click_resolution;
236 int delta= clicks - last_encoder_click; // the number of clicks this time
237 last_encoder_click= clicks;
b6e81799 238
10d11dfd 239 if ( delta != 0 ) {
58d6d841 240 this->counter_changed = true;
10d11dfd 241 (*this->counter) += delta;
862fc625 242 this->idle_time = 0;
35089dc7 243 }
58d6d841 244 return 0;
35089dc7
JM
245}
246
247// Read and update each button
862fc625
JM
248uint32_t Panel::button_tick(uint32_t dummy)
249{
58d6d841
JM
250 this->do_buttons = true;
251 return 0;
35089dc7
JM
252}
253
e456d044
JM
254// Read and update encoder
255uint32_t Panel::encoder_tick(uint32_t dummy)
256{
257 this->do_encoder = true;
258 return 0;
259}
260
c89338bd 261void Panel::on_set_public_data(void *argument)
399cb110 262{
c89338bd
JM
263 PublicDataRequest *pdr = static_cast<PublicDataRequest *>(argument);
264
265 if(!pdr->starts_with(panel_checksum)) return;
266
267 if(!pdr->second_element_is(panel_display_message_checksum)) return;
268
269 string *s = static_cast<string *>(pdr->get_data_ptr());
270 if (s->size() > 20) {
271 this->message = s->substr(0, 20);
272 } else {
273 this->message= *s;
399cb110
JM
274 }
275}
276
35089dc7 277// on main loop, we can send gcodes or do anything that waits in this loop
862fc625
JM
278void Panel::on_main_loop(void *argument)
279{
280 if (this->current_screen != NULL) {
f140cf3a
JM
281 this->current_screen->on_main_loop();
282 this->lcd->on_main_loop();
283 }
35089dc7
JM
284}
285
c96fd70a
JM
286
287#define ohw_logo_antipixel_width 80
288#define ohw_logo_antipixel_height 15
161164be 289static const uint8_t ohw_logo_antipixel_bits[] = {
862fc625
JM
290 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
291 0x00, 0x00, 0x00, 0x01, 0x80, 0x0C, 0x00, 0x33, 0x18, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x5E,
292 0x80, 0x2D, 0x6B, 0x9B, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x18, 0xAB, 0xFF, 0xFF,
293 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x7B, 0xB3, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x7F, 0x80, 0x33,
294 0x78, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x81, 0xF3, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD,
295 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xB3, 0x18, 0xDD, 0x98, 0xC5, 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xAD,
296 0x6B, 0x5D, 0x6B, 0x5D, 0x80, 0x73, 0x80, 0x3F, 0xFC, 0x21, 0x1B, 0x55, 0x08, 0xC5, 0x80, 0xF3,
297 0xC0, 0x3F, 0xFD, 0xAD, 0x5B, 0x49, 0x6A, 0xDD, 0x80, 0xE1, 0xC0, 0x3F, 0xFD, 0xAD, 0x68, 0xDD,
298 0x6B, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
299 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
c96fd70a
JM
300};
301
35089dc7
JM
302// On idle things, we don't want to do shit in interrupts
303// don't queue gcodes in this
862fc625
JM
304void Panel::on_idle(void *argument)
305{
306 if (this->start_up) {
f140cf3a
JM
307 this->lcd->init();
308
ebc64506
JM
309 Version v;
310 string build(v.get_build());
311 string date(v.get_build_date());
312 this->lcd->clear();
862fc625
JM
313 this->lcd->setCursor(0, 0); this->lcd->printf("Welcome to Smoothie");
314 this->lcd->setCursor(0, 1); this->lcd->printf("%s", build.substr(0, 20).c_str());
315 this->lcd->setCursor(0, 2); this->lcd->printf("%s", date.substr(0, 20).c_str());
316 this->lcd->setCursor(0, 3); this->lcd->printf("Please wait....");
ebc64506 317
862fc625 318 if (this->lcd->hasGraphics()) {
c96fd70a
JM
319 this->lcd->bltGlyph(24, 40, ohw_logo_antipixel_width, ohw_logo_antipixel_height, ohw_logo_antipixel_bits);
320 }
321
f54d25cb
JM
322 this->lcd->on_refresh(true); // tell lcd to display now
323
f140cf3a 324 // Default top screen
ca6effd7 325 this->top_screen= new MainMenuScreen();
ca6effd7 326 this->custom_screen->set_parent(this->top_screen);
862fc625 327 this->start_up = false;
5ff3e912 328 return;
f140cf3a 329 }
35089dc7 330
ca6effd7 331 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
58d6d841 332 // after being idle for a while switch to Watch screen
5ff3e912 333 if (this->current_screen != NULL && this->idle_time > this->current_screen->idle_timeout_secs()*20) {
862fc625 334 this->idle_time = 0;
ca6effd7
JM
335 if (mms->watch_screen != this->current_screen) {
336 this->enter_screen(mms->watch_screen);
58d6d841
JM
337 // TODO do we need to reset any state?
338 }
399cb110 339
58d6d841
JM
340 return;
341 }
399cb110 342
5ff3e912
JM
343 if(current_screen == NULL && this->idle_time > 20*4) {
344 this->enter_screen(mms->watch_screen);
345 return;
346 }
347
e456d044
JM
348 if(this->do_encoder) {
349 this->do_encoder= false;
350 encoder_check(0);
351 }
352
862fc625 353 if (this->do_buttons) {
e456d044 354 // we don't want to do SPI in interrupt mode
58d6d841
JM
355 this->do_buttons = false;
356
357 // read the actual buttons
862fc625
JM
358 int but = lcd->readButtons();
359 if (but != 0) {
360 this->idle_time = 0;
5ff3e912
JM
361 if(current_screen == NULL) {
362 // we were in startup screen so go to watch screen
363 this->enter_screen(mms->watch_screen);
364 return;
365 }
f140cf3a
JM
366 }
367
58d6d841 368 // fire events if the buttons are active and debounce is satisfied
862fc625
JM
369 this->up_button.check_signal(but & BUTTON_UP);
370 this->down_button.check_signal(but & BUTTON_DOWN);
371 this->back_button.check_signal(but & BUTTON_LEFT);
372 this->click_button.check_signal(but & BUTTON_SELECT);
862fc625 373 }
399cb110 374
35089dc7 375 // If we are in menu mode and the position has changed
862fc625 376 if ( this->mode == MENU_MODE && this->counter_change() ) {
35089dc7
JM
377 this->menu_update();
378 }
379
380 // If we are in control mode
862fc625 381 if ( this->mode == CONTROL_MODE && this->counter_change() ) {
58d6d841 382 this->control_value_update();
35089dc7
JM
383 }
384
385 // If we must refresh
862fc625 386 if ( this->refresh_flag ) {
35089dc7 387 this->refresh_flag = false;
862fc625 388 if (this->current_screen != NULL) {
f140cf3a
JM
389 this->current_screen->on_refresh();
390 this->lcd->on_refresh();
391 }
35089dc7
JM
392 }
393}
394
395// Hooks for button clicks
862fc625
JM
396uint32_t Panel::on_up(uint32_t dummy)
397{
25121d12 398 // this is simulating encoder clicks, but needs to be inverted to
b6e81799
JM
399 // increment values on up,increment by
400 int inc = (this->mode == CONTROL_MODE) ? 1 : -(this->menu_offset+1);
25121d12 401 *this->counter += inc;
58d6d841
JM
402 this->counter_changed = true;
403 return 0;
35089dc7 404}
5ff3e912 405
862fc625
JM
406uint32_t Panel::on_down(uint32_t dummy)
407{
b6e81799 408 int inc = (this->mode == CONTROL_MODE) ? -1 : (this->menu_offset+1);
25121d12 409 *this->counter += inc;
58d6d841
JM
410 this->counter_changed = true;
411 return 0;
35089dc7
JM
412}
413
414// on most menu screens will go back to previous higher menu
862fc625
JM
415uint32_t Panel::on_back(uint32_t dummy)
416{
417 if (this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) {
58d6d841
JM
418 this->enter_screen(this->current_screen->parent);
419 }
420 return 0;
35089dc7
JM
421}
422
862fc625
JM
423uint32_t Panel::on_select(uint32_t dummy)
424{
58d6d841
JM
425 // TODO make configurable, including turning off
426 // buzz is ignored on panels that do not support buzz
58d6d841 427 this->click_changed = true;
862fc625
JM
428 this->idle_time = 0;
429 lcd->buzz(60, 300); // 50ms 300Hz
58d6d841 430 return 0;
35089dc7
JM
431}
432
862fc625
JM
433bool Panel::counter_change()
434{
435 if ( this->counter_changed ) {
436 this->counter_changed = false;
437 return true;
438 } else {
439 return false;
440 }
441}
442bool Panel::click()
443{
444 if ( this->click_changed ) {
445 this->click_changed = false;
446 return true;
447 } else {
448 return false;
449 }
450}
35089dc7
JM
451
452
453// Enter menu mode
5971142d 454void Panel::enter_menu_mode(bool force)
862fc625 455{
35089dc7
JM
456 this->mode = MENU_MODE;
457 this->counter = &this->menu_selected_line;
5971142d 458 this->menu_changed = force;
35089dc7
JM
459}
460
862fc625
JM
461void Panel::setup_menu(uint16_t rows)
462{
f65ce58f
JM
463 this->setup_menu(rows, min(rows, this->max_screen_lines()));
464}
465
862fc625
JM
466void Panel::setup_menu(uint16_t rows, uint16_t lines)
467{
35089dc7 468 this->menu_selected_line = 0;
862fc625 469 this->menu_current_line = 0;
399cb110 470 this->menu_start_line = 0;
35089dc7 471 this->menu_rows = rows;
206167cf 472 this->panel_lines = lines;
35089dc7
JM
473}
474
862fc625
JM
475void Panel::menu_update()
476{
399cb110 477 // Limits, up and down
446deda2 478 // NOTE menu_selected_line is changed in an interrupt and can change at any time
862fc625 479 int msl = this->menu_selected_line; // hopefully this is atomic
206167cf
JM
480
481 #if 0
482 // this allows it to wrap but with new method we dont; want to wrap
862fc625
JM
483 msl = msl % ( this->menu_rows << this->menu_offset );
484 while ( msl < 0 ) {
485 msl += this->menu_rows << this->menu_offset;
486 }
206167cf
JM
487 #else
488 // limit selected line to screen lines available
489 if(msl >= this->menu_rows<<this->menu_offset){
490 msl= (this->menu_rows-1)<<this->menu_offset;
491 }else if(msl < 0) msl= 0;
492 #endif
446deda2 493
206167cf 494 this->menu_selected_line = msl; // update atomically we hope
b6e81799
JM
495 // figure out which actual line to select, if we have a menu offset it means we want to move one line per two clicks
496 if(msl % (this->menu_offset+1) == 0) { // only if divisible by offset
497 this->menu_current_line = msl >> this->menu_offset;
498 }
35089dc7
JM
499
500 // What to display
206167cf
JM
501 if ( this->menu_rows > this->panel_lines ) {
502 #if 0
503 // old way of scrolling not nice....
862fc625 504 if ( this->menu_current_line >= 2 ) {
446deda2 505 this->menu_start_line = this->menu_current_line - 1;
35089dc7 506 }
206167cf
JM
507 if ( this->menu_current_line > this->menu_rows - this->panel_lines ) {
508 this->menu_start_line = this->menu_rows - this->panel_lines;
509 }
510 #else
511 // new way we only scroll the lines when the cursor hits the bottom of the screen or the top of the screen
512 // do we want to scroll up?
513 int sl= this->menu_current_line - this->menu_start_line; // screen line we are on
514 if(sl >= this->panel_lines) {
515 this->menu_start_line += ((sl+1)-this->panel_lines); // scroll up to keep it on the screen
516
517 }else if(sl < 0 ) { // do we want to scroll down?
518 this->menu_start_line += sl; // scroll down
35089dc7 519 }
206167cf
JM
520 #endif
521
522 }else{
523 this->menu_start_line = 0;
35089dc7
JM
524 }
525
526 this->menu_changed = true;
527}
528
862fc625
JM
529bool Panel::menu_change()
530{
531 if ( this->menu_changed ) {
532 this->menu_changed = false;
533 return true;
534 } else {
535 return false;
536 }
35089dc7
JM
537}
538
862fc625
JM
539bool Panel::control_value_change()
540{
541 if ( this->control_value_changed ) {
542 this->control_value_changed = false;
543 return true;
544 } else {
545 return false;
546 }
35089dc7
JM
547}
548
1ad23cd3 549bool Panel::enter_control_mode(float passed_normal_increment, float passed_pressed_increment)
862fc625 550{
35089dc7
JM
551 this->mode = CONTROL_MODE;
552 this->normal_increment = passed_normal_increment;
35089dc7
JM
553 this->counter = &this->control_normal_counter;
554 this->control_normal_counter = 0;
58d6d841
JM
555 this->control_base_value = 0;
556 return true;
35089dc7
JM
557}
558
862fc625
JM
559void Panel::control_value_update()
560{
58d6d841
JM
561 // TODO what do we do here?
562 this->control_value_changed = true;
35089dc7
JM
563}
564
1ad23cd3 565void Panel::set_control_value(float value)
862fc625 566{
58d6d841 567 this->control_base_value = value;
35089dc7
JM
568}
569
1ad23cd3 570float Panel::get_control_value()
862fc625
JM
571{
572 return this->control_base_value + (this->control_normal_counter * this->normal_increment);
35089dc7
JM
573}
574
862fc625
JM
575bool Panel::is_playing() const
576{
58d6d841
JM
577 void *returned_data;
578
75e6428d 579 bool ok = PublicData::get_value( player_checksum, is_playing_checksum, &returned_data );
862fc625
JM
580 if (ok) {
581 bool b = *static_cast<bool *>(returned_data);
58d6d841
JM
582 return b;
583 }
584 return false;
7aa9fef3
JM
585}
586
5f1a896b
JM
587bool Panel::is_suspended() const
588{
589 void *returned_data;
590
591 bool ok = PublicData::get_value( player_checksum, is_suspended_checksum, &returned_data );
592 if (ok) {
593 bool b = *static_cast<bool *>(returned_data);
594 return b;
595 }
596 return false;
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 607
fb877329
JM
608bool Panel::mount_external_sd(bool on)
609{
610 // now setup the external sdcard if we have one and mount it
611 if(on) {
612 if(this->sd == nullptr) {
613 PinName mosi, miso, sclk, cs= this->extsd_spi_cs;
614 if(extsd_spi_channel == 0) {
615 mosi = P0_18; miso = P0_17; sclk = P0_15;
616 } else if(extsd_spi_channel == 1) {
617 mosi = P0_9; miso = P0_8; sclk = P0_7;
618 } else{
619 this->external_sd_enable= false;
620 THEKERNEL->streams->printf("Bad SPI channel for external SDCard\n");
621 return false;
622 }
623 size_t n= sizeof(SDCard);
624 void *v = AHB0.alloc(n);
625 memset(v, 0, n); // clear the allocated memory
626 this->sd= new(v) SDCard(mosi, miso, sclk, cs); // allocate object using zeroed memory
627 }
628 delete this->extmounter; // if it was not unmounted before
629 size_t n= sizeof(SDFAT);
630 void *v = AHB0.alloc(n);
631 memset(v, 0, n); // clear the allocated memory
632 this->extmounter= new(v) SDFAT("ext", this->sd); // use cleared allocated memory
633 this->sd->disk_initialize(); // first one seems to fail, but works next time
634 THEKERNEL->streams->printf("External SDcard mounted as /ext\n");
635 }else{
636 delete this->extmounter;
637 this->extmounter= nullptr;
638 THEKERNEL->streams->printf("External SDcard unmounted\n");
639 }
640 return true;
641}
642
643void Panel::on_second_tick(void *arg)
644{
892508b8 645 if(!this->external_sd_enable || this->start_up) return;
fb877329
JM
646
647 // sd insert detect, mount sdcard if inserted, unmount if removed
648 if(this->sdcd_pin.connected()) {
649 if(this->extmounter == nullptr && this->sdcd_pin.get()) {
650 mount_external_sd(true);
651 // go to the play screen and the /ext directory
892508b8 652 // TODO we don't want to do this if we just booted and card was already in
fb877329
JM
653 THEKERNEL->current_path= "/ext";
654 MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
655 THEPANEL->enter_screen(mms->file_screen);
656
657 }else if(this->extmounter != nullptr && !this->sdcd_pin.get()){
658 mount_external_sd(false);
659 }
660 }else{
661 // TODO for panels with no sd card detect we need to poll to see if card is inserted - or not
662 }
663}