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" | |
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 |
68 | Panel* Panel::instance= nullptr; |
69 | ||
862fc625 JM |
70 | Panel::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 |
89 | Panel::~Panel() |
90 | { | |
58d6d841 | 91 | delete this->lcd; |
fb877329 JM |
92 | delete this->extmounter; |
93 | delete this->sd; | |
35089dc7 JM |
94 | } |
95 | ||
862fc625 JM |
96 | void Panel::on_module_loaded() |
97 | { | |
35089dc7 | 98 | // Exit if this module is not enabled |
314ab8f7 | 99 | if ( !THEKERNEL->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ) { |
58d6d841 JM |
100 | delete this; |
101 | return; | |
399cb110 | 102 | } |
35089dc7 | 103 | |
58d6d841 JM |
104 | // Initialise the LCD, see which LCD to use |
105 | if (this->lcd != NULL) delete this->lcd; | |
7713b1ef | 106 | int lcd_cksm = get_checksum(THEKERNEL->config->value(panel_checksum, lcd_checksum)->by_default("reprap_discount_glcd")->as_string()); |
58d6d841 JM |
107 | |
108 | // Note checksums are not const expressions when in debug mode, so don't use switch | |
7713b1ef | 109 | if (lcd_cksm == rrd_glcd_checksum) { |
357d0eda | 110 | this->lcd = new ReprapDiscountGLCD(); |
862fc625 | 111 | } else if (lcd_cksm == st7565_glcd_checksum) { |
84e7fd5a | 112 | this->lcd = new ST7565(); |
8b6bc932 | 113 | } else if (lcd_cksm == viki2_checksum) { |
8ed554f3 | 114 | this->lcd = new ST7565(1); // variant 1 |
8b6bc932 | 115 | } else if (lcd_cksm == mini_viki2_checksum) { |
8ed554f3 | 116 | this->lcd = new ST7565(2); // variant 2 |
3ae0e673 JM |
117 | } else if (lcd_cksm == universal_adapter_checksum) { |
118 | this->lcd = new UniversalAdapter(); | |
862fc625 | 119 | } else { |
7713b1ef JM |
120 | // no known lcd type defined |
121 | delete this; | |
58d6d841 JM |
122 | return; |
123 | } | |
124 | ||
fb877329 JM |
125 | // external sd |
126 | if(THEKERNEL->config->value( panel_checksum, ext_sd_checksum )->by_default(false)->as_bool()) { | |
127 | this->external_sd_enable= true; | |
128 | // external sdcard detect | |
129 | this->sdcd_pin.from_string(THEKERNEL->config->value( panel_checksum, ext_sd_checksum, sdcd_pin_checksum )->by_default("nc")->as_string())->as_input(); | |
130 | this->extsd_spi_channel = THEKERNEL->config->value(panel_checksum, ext_sd_checksum, spi_channel_checksum)->by_default(0)->as_number(); | |
131 | string s= THEKERNEL->config->value( panel_checksum, ext_sd_checksum, spi_cs_pin_checksum)->by_default("2.8")->as_string(); | |
132 | s= "P" + s; // Pinnames need to be Px_x | |
133 | this->extsd_spi_cs= parse_pins(s.c_str()); | |
134 | this->register_for_event(ON_SECOND_TICK); | |
135 | } | |
136 | ||
cee1bb2d JM |
137 | // these need to be called here as they need the config cache loaded as they enumerate modules |
138 | this->custom_screen= new CustomScreen(); | |
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 |
197 | void 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 |
208 | void 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 |
216 | uint32_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 |
224 | uint32_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 |
248 | uint32_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 |
255 | uint32_t Panel::encoder_tick(uint32_t dummy) | |
256 | { | |
257 | this->do_encoder = true; | |
258 | return 0; | |
259 | } | |
260 | ||
c89338bd | 261 | void 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 |
278 | void 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 | 289 | static 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 |
304 | void 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 |
396 | uint32_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 |
406 | uint32_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 |
415 | uint32_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 |
423 | uint32_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 |
433 | bool Panel::counter_change() |
434 | { | |
435 | if ( this->counter_changed ) { | |
436 | this->counter_changed = false; | |
437 | return true; | |
438 | } else { | |
439 | return false; | |
440 | } | |
441 | } | |
442 | bool 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 | 454 | void 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 |
461 | void Panel::setup_menu(uint16_t rows) |
462 | { | |
f65ce58f JM |
463 | this->setup_menu(rows, min(rows, this->max_screen_lines())); |
464 | } | |
465 | ||
862fc625 JM |
466 | void 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 |
475 | void 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 |
529 | bool 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 |
539 | bool 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 | 549 | bool 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 |
559 | void 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 | 565 | void Panel::set_control_value(float value) |
862fc625 | 566 | { |
58d6d841 | 567 | this->control_base_value = value; |
35089dc7 JM |
568 | } |
569 | ||
1ad23cd3 | 570 | float 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 |
575 | bool 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 |
587 | bool 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 |
599 | void Panel::set_playing_file(string f) |
600 | { | |
7aa9fef3 | 601 | // just copy the first 20 characters after the first / if there |
862fc625 JM |
602 | size_t n = f.find_last_of('/'); |
603 | if (n == string::npos) n = 0; | |
604 | strncpy(playing_file, f.substr(n + 1, 19).c_str(), sizeof(playing_file)); | |
605 | playing_file[sizeof(playing_file) - 1] = 0; | |
7aa9fef3 | 606 | } |
cee1bb2d | 607 | |
fb877329 JM |
608 | bool 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 | ||
643 | void 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 | } |