add glyph blit operatin to gcld
[clinton/Smoothieware.git] / src / modules / utils / panel / Panel.cpp
CommitLineData
35089dc7
JM
1/*
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.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
6*/
7
8#include "libs/Kernel.h"
9#include "Panel.h"
10#include "libs/nuts_bolts.h"
11#include "libs/utils.h"
12#include <string>
13using namespace std;
14#include "Button.h"
15#include "PanelScreen.h"
16#include "screens/MainMenuScreen.h"
17#include "modules/utils/player/PlayerPublicAccess.h"
18
19#include "panels/I2CLCD.h"
20#include "panels/VikiLCD.h"
1c2b594a 21#include "panels/Smoothiepanel.h"
357d0eda 22#include "panels/ReprapDiscountGLCD.h"
f140cf3a 23#include "version.h"
35089dc7
JM
24
25Panel::Panel(){
26 this->counter_changed = false;
27 this->click_changed = false;
28 this->refresh_flag = false;
29 this->enter_menu_mode();
58d6d841
JM
30 this->lcd= NULL;
31 this->do_buttons = false;
32 this->idle_time= 0;
f140cf3a
JM
33 this->start_up= true;
34 this->current_screen= NULL;
7aa9fef3 35 strcpy(this->playing_file, "Playing file");
35089dc7
JM
36}
37
38Panel::~Panel() {
58d6d841 39 delete this->lcd;
35089dc7
JM
40}
41
42void Panel::on_module_loaded(){
43 // Exit if this module is not enabled
58d6d841
JM
44 if( !this->kernel->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ){
45 delete this;
46 return;
47 }
35089dc7 48
58d6d841
JM
49 // Initialise the LCD, see which LCD to use
50 if (this->lcd != NULL) delete this->lcd;
51 int lcd_cksm = get_checksum(this->kernel->config->value(panel_checksum, lcd_checksum)->by_default("i2c")->as_string());
52
53 // Note checksums are not const expressions when in debug mode, so don't use switch
54 if(lcd_cksm == i2c_lcd_checksum) {
55 this->lcd = new I2CLCD();
56 }else if(lcd_cksm == viki_lcd_checksum) {
57 this->lcd = new VikiLCD();
e0107f6b
JM
58 this->lcd->set_variant(0);
59 }else if(lcd_cksm == panelolu2_checksum) {
60 this->lcd = new VikiLCD();
61 this->lcd->set_variant(1);
1c2b594a
L
62 }else if(lcd_cksm == smoothiepanel_checksum) {
63 this->lcd = new Smoothiepanel();
357d0eda
JM
64 }else if(lcd_cksm == rrd_glcd_checksum) {
65 this->lcd = new ReprapDiscountGLCD();
58d6d841
JM
66 }else{
67 // no lcd type defined
68 return;
69 }
70
71 // some panels may need access to this global info
72 this->lcd->setPanel(this);
73
f65ce58f
JM
74 // the number of screen lines the panel supports
75 this->screen_lines= this->lcd->get_screen_lines();
76
19fb4629
JM
77 // some encoders may need more clicks to move menu, this is a divisor and is in config as it is
78 // an end user usability issue
79 this->menu_offset = this->kernel->config->value( panel_checksum, menu_offset_checksum )->by_default(0)->as_number();
80
58d6d841
JM
81 // load jogging feedrates in mm/min
82 jogging_speed_mm_min[0]= this->kernel->config->value( panel_checksum, jog_x_feedrate_checksum )->by_default(3000.0)->as_number();
83 jogging_speed_mm_min[1]= this->kernel->config->value( panel_checksum, jog_y_feedrate_checksum )->by_default(3000.0)->as_number();
84 jogging_speed_mm_min[2]= this->kernel->config->value( panel_checksum, jog_z_feedrate_checksum )->by_default(300.0)->as_number();
f19a841a
JM
85
86 // load the default preset temeratures
87 default_hotend_temperature= this->kernel->config->value( panel_checksum, hotend_temp_checksum )->by_default(185.0)->as_number();
88 default_bed_temperature= this->kernel->config->value( panel_checksum, bed_temp_checksum )->by_default(60.0)->as_number();
89
58d6d841 90 this->encoder_click_resolution= this->lcd->getEncoderResolution();
58d6d841 91
95195a29
JM
92 this->up_button.up_attach( this, &Panel::on_up );
93 this->down_button.up_attach( this, &Panel::on_down );
ab4abea9 94 this->click_button.up_attach( this, &Panel::on_select );
95195a29 95 this->back_button.up_attach( this, &Panel::on_back );
52500e58 96 this->pause_button.up_attach( this, &Panel::on_pause );
35089dc7 97
4933a6ed
L
98 this->kernel->slow_ticker->attach( 100, this, &Panel::button_tick );
99 this->kernel->slow_ticker->attach( 1000, this, &Panel::encoder_check );
f140cf3a 100
767e7c51
JM
101 // Register for events
102 this->register_for_event(ON_IDLE);
103 this->register_for_event(ON_MAIN_LOOP);
104
35089dc7
JM
105 // Refresh timer
106 this->kernel->slow_ticker->attach( 20, this, &Panel::refresh_tick );
35089dc7
JM
107}
108
109// Enter a screen, we only care about it now
110void Panel::enter_screen(PanelScreen* screen){
111 screen->panel = this;
112 this->current_screen = screen;
113 this->reset_counter();
114 this->current_screen->on_enter();
115}
116
117// Reset the counter
118void Panel::reset_counter(){
119 *this->counter = 0;
120 this->counter_changed = false;
121}
122
123// Indicate the idle loop we want to call the refresh hook in the current screen
124uint32_t Panel::refresh_tick(uint32_t dummy){
58d6d841
JM
125 this->refresh_flag = true;
126 this->idle_time++;
127 return 0;
35089dc7
JM
128}
129
130// Encoder pins changed
131uint32_t Panel::encoder_check(uint32_t dummy){
58d6d841
JM
132 // TODO if encoder reads go through i2c like on smoothie panel this needs to be
133 // optionally done in idle loop, however when reading encoder directly it needs to be done
134 // frequently, smoothie panel will return an actual delta count so won't miss any if polled slowly
135 static int encoder_counter = 0;
35089dc7 136 int change = lcd->readEncoderDelta();
58d6d841
JM
137 encoder_counter += change;
138 // TODO divisor needs to be configurable
4933a6ed 139 if( change != 0 && encoder_counter % this->encoder_click_resolution == 0 ){
58d6d841
JM
140 this->counter_changed = true;
141 (*this->counter) += change;
142 this->idle_time= 0;
35089dc7 143 }
58d6d841 144 return 0;
35089dc7
JM
145}
146
147// Read and update each button
148uint32_t Panel::button_tick(uint32_t dummy){
58d6d841
JM
149 this->do_buttons = true;
150 return 0;
35089dc7
JM
151}
152
153// on main loop, we can send gcodes or do anything that waits in this loop
154void Panel::on_main_loop(void* argument){
f140cf3a
JM
155 if(this->current_screen != NULL) {
156 this->current_screen->on_main_loop();
157 this->lcd->on_main_loop();
158 }
35089dc7
JM
159}
160
161// On idle things, we don't want to do shit in interrupts
162// don't queue gcodes in this
163void Panel::on_idle(void* argument){
f140cf3a
JM
164 if(this->start_up) {
165 this->lcd->init();
166
167 if(this->lcd->hasGraphics()) {
f54d25cb
JM
168 // TODO display nifty graphics startup screen
169
f140cf3a
JM
170 }
171
ebc64506
JM
172 Version v;
173 string build(v.get_build());
174 string date(v.get_build_date());
175 this->lcd->clear();
176 this->lcd->setCursor(0,0); this->lcd->printf("Welcome to Smoothie");
177 this->lcd->setCursor(0,1); this->lcd->printf("%s", build.substr(0, 20).c_str());
178 this->lcd->setCursor(0,2); this->lcd->printf("%s", date.substr(0, 20).c_str());
179 this->lcd->setCursor(0,3); this->lcd->printf("Please wait....");
180
f54d25cb
JM
181 this->lcd->on_refresh(true); // tell lcd to display now
182
f140cf3a
JM
183 // Default top screen
184 this->top_screen = new MainMenuScreen();
185 this->top_screen->set_panel(this);
186 this->start_up= false;
f54d25cb 187 this->idle_time= 20*3; // only show for 2 seconds
f140cf3a 188 }
35089dc7 189
58d6d841
JM
190 // after being idle for a while switch to Watch screen
191 if(this->idle_time > 20*5) { // 5 seconds
192 this->idle_time= 0;
193 if(this->top_screen->watch_screen != this->current_screen) {
194 this->enter_screen(this->top_screen->watch_screen);
195 // TODO do we need to reset any state?
196 }
197
198 return;
199 }
200
201 if(this->do_buttons) {
202 // we don't want to do I2C in interrupt mode
203 this->do_buttons = false;
204
205 // read the actual buttons
206 int but= lcd->readButtons();
f140cf3a
JM
207 if(but != 0){
208 this->idle_time= 0;
209 }
210
58d6d841
JM
211 // fire events if the buttons are active and debounce is satisfied
212 this->up_button.check_signal(but&BUTTON_UP);
213 this->down_button.check_signal(but&BUTTON_DOWN);
214 this->back_button.check_signal(but&BUTTON_LEFT);
215 this->click_button.check_signal(but&BUTTON_SELECT);
52500e58 216 this->pause_button.check_signal(but&BUTTON_PAUSE);
f140cf3a 217 }
58d6d841 218
35089dc7
JM
219 // If we are in menu mode and the position has changed
220 if( this->mode == MENU_MODE && this->counter_change() ){
221 this->menu_update();
222 }
223
224 // If we are in control mode
225 if( this->mode == CONTROL_MODE && this->counter_change() ){
58d6d841 226 this->control_value_update();
35089dc7
JM
227 }
228
229 // If we must refresh
230 if( this->refresh_flag ){
231 this->refresh_flag = false;
f140cf3a
JM
232 if(this->current_screen != NULL) {
233 this->current_screen->on_refresh();
234 this->lcd->on_refresh();
235 }
35089dc7
JM
236 }
237}
238
239// Hooks for button clicks
240uint32_t Panel::on_up(uint32_t dummy){
25121d12
JM
241 // this is simulating encoder clicks, but needs to be inverted to
242 // increment values on up
243 int inc= (this->mode == CONTROL_MODE) ? 1 : -1;
244 *this->counter += inc;
58d6d841
JM
245 this->counter_changed = true;
246 return 0;
35089dc7
JM
247}
248uint32_t Panel::on_down(uint32_t dummy){
25121d12
JM
249 int inc= (this->mode == CONTROL_MODE) ? -1 : 1;
250 *this->counter += inc;
58d6d841
JM
251 this->counter_changed = true;
252 return 0;
35089dc7
JM
253}
254
255// on most menu screens will go back to previous higher menu
256uint32_t Panel::on_back(uint32_t dummy){
f140cf3a 257 if(this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) {
58d6d841
JM
258 this->enter_screen(this->current_screen->parent);
259 }
260 return 0;
35089dc7
JM
261}
262
ab4abea9 263uint32_t Panel::on_select(uint32_t dummy){
58d6d841
JM
264 // TODO make configurable, including turning off
265 // buzz is ignored on panels that do not support buzz
58d6d841
JM
266 this->click_changed = true;
267 this->idle_time= 0;
073389cb 268 lcd->buzz(60,300); // 50ms 300Hz
58d6d841 269 return 0;
35089dc7
JM
270}
271
52500e58
L
272uint32_t Panel::on_pause(uint32_t dummy){
273 if(!paused) {
274 THEKERNEL->pauser->take();
275 paused= true;
276 }else{
277 THEKERNEL->pauser->release();
278 paused= false;
279 }
280 return 0;
281}
282
35089dc7
JM
283bool Panel::counter_change(){ if( this->counter_changed ){ this->counter_changed = false; return true; }else{ return false; } }
284bool Panel::click(){ if( this->click_changed ){ this->click_changed = false; return true; }else{ return false; } }
285
286
287// Enter menu mode
288void Panel::enter_menu_mode(){
289 this->mode = MENU_MODE;
290 this->counter = &this->menu_selected_line;
291 this->menu_changed = false;
292}
293
f65ce58f
JM
294void Panel::setup_menu(uint16_t rows){
295 this->setup_menu(rows, min(rows, this->max_screen_lines()));
296}
297
35089dc7
JM
298void Panel::setup_menu(uint16_t rows, uint16_t lines){
299 this->menu_selected_line = 0;
300 this->menu_start_line = 0;
301 this->menu_rows = rows;
302 this->menu_lines = lines;
303}
304
305uint16_t Panel::menu_current_line(){
306 return this->menu_selected_line >> this->menu_offset;
307}
308
309void Panel::menu_update(){
310
311 // Limits, up and down
312 this->menu_selected_line = this->menu_selected_line % ( this->menu_rows<<this->menu_offset );
313 while( this->menu_selected_line < 0 ){ this->menu_selected_line += this->menu_rows << this->menu_offset; }
314
315 // What to display
316 this->menu_start_line = 0;
317 if( this->menu_rows > this->menu_lines ){
318 if( this->menu_current_line() >= 2 ){
319 this->menu_start_line = this->menu_current_line() - 1;
320 }
321 if( this->menu_current_line() > this->menu_rows - this->menu_lines ){
322 this->menu_start_line = this->menu_rows - this->menu_lines;
323 }
324 }
325
326 this->menu_changed = true;
327}
328
329bool Panel::menu_change(){
330 if( this->menu_changed ){ this->menu_changed = false; return true; }else{ return false; }
331}
332
333bool Panel::control_value_change(){
58d6d841 334 if( this->control_value_changed ){ this->control_value_changed = false; return true; }else{ return false; }
35089dc7
JM
335}
336
337
338bool Panel::enter_control_mode(double passed_normal_increment, double passed_pressed_increment){
339 this->mode = CONTROL_MODE;
340 this->normal_increment = passed_normal_increment;
341 this->pressed_increment = passed_pressed_increment;
342 this->counter = &this->control_normal_counter;
343 this->control_normal_counter = 0;
344 this->control_pressed_counter = 0;
58d6d841
JM
345 this->control_base_value = 0;
346 return true;
35089dc7
JM
347}
348
349void Panel::control_value_update(){
58d6d841
JM
350 // TODO what do we do here?
351 this->control_value_changed = true;
35089dc7
JM
352}
353
354void Panel::set_control_value(double value){
58d6d841 355 this->control_base_value = value;
35089dc7
JM
356}
357
358double Panel::get_control_value(){
19fb4629 359 return this->control_base_value + (this->control_normal_counter*this->normal_increment);
35089dc7
JM
360}
361
362bool Panel::is_playing() const {
58d6d841
JM
363 void *returned_data;
364
365 bool ok= THEKERNEL->public_data->get_value( player_checksum, is_playing_checksum, &returned_data );
366 if(ok) {
367 bool b= *static_cast<bool*>(returned_data);
368 return b;
369 }
370 return false;
7aa9fef3
JM
371}
372
373void Panel::set_playing_file(string f) {
374 // just copy the first 20 characters after the first / if there
375 size_t n= f.find_last_of('/');
376 if(n == string::npos) n= 0;
377 strncpy(playing_file, f.substr(n+1, 19).c_str(), sizeof(playing_file));
378 playing_file[sizeof(playing_file)-1]= 0;
379}