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