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" |
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 |
69 | Panel* Panel::instance= nullptr; |
70 | ||
862fc625 JM |
71 | Panel::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 |
90 | Panel::~Panel() |
91 | { | |
58d6d841 | 92 | delete this->lcd; |
fb877329 JM |
93 | delete this->extmounter; |
94 | delete this->sd; | |
35089dc7 JM |
95 | } |
96 | ||
862fc625 JM |
97 | void 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 |
199 | void 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 |
210 | void 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 |
218 | uint32_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 |
226 | uint32_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 |
250 | uint32_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 |
257 | uint32_t Panel::encoder_tick(uint32_t dummy) | |
258 | { | |
259 | this->do_encoder = true; | |
260 | return 0; | |
261 | } | |
262 | ||
c89338bd | 263 | void 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 |
280 | void 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 | 291 | static 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 |
306 | void 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 |
399 | uint32_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 |
409 | uint32_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 |
418 | uint32_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 |
426 | uint32_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 |
436 | uint32_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 |
446 | bool Panel::counter_change() |
447 | { | |
448 | if ( this->counter_changed ) { | |
449 | this->counter_changed = false; | |
450 | return true; | |
451 | } else { | |
452 | return false; | |
453 | } | |
454 | } | |
455 | bool 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 | 467 | void 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 |
474 | void Panel::setup_menu(uint16_t rows) |
475 | { | |
f65ce58f JM |
476 | this->setup_menu(rows, min(rows, this->max_screen_lines())); |
477 | } | |
478 | ||
862fc625 JM |
479 | void 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 |
488 | void 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 |
542 | bool 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 |
552 | bool 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 | 562 | bool 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 |
572 | void 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 | 578 | void Panel::set_control_value(float value) |
862fc625 | 579 | { |
58d6d841 | 580 | this->control_base_value = value; |
35089dc7 JM |
581 | } |
582 | ||
1ad23cd3 | 583 | float 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 |
588 | bool 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 |
600 | bool 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 |
612 | void 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 |
621 | bool 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 | ||
656 | void 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 | } |