Fork of Smoothie to port to mbed non-LPC targets.

Dependencies:   mbed

Fork of Smoothie by Stéphane Cachat

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers Panel.cpp Source File

Panel.cpp

00001 /*
00002       This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
00003       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.
00004       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.
00005       You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
00006 */
00007 
00008 #include "libs/Kernel.h"
00009 #include "Panel.h"
00010 #include "PanelScreen.h"
00011 
00012 #include "libs/nuts_bolts.h"
00013 #include "libs/utils.h"
00014 #include "Button.h"
00015 
00016 #include "modules/utils/player/PlayerPublicAccess.h"
00017 #include "screens/CustomScreen.h"
00018 #include "screens/MainMenuScreen.h"
00019 
00020 #include "panels/I2CLCD.h"
00021 #include "panels/VikiLCD.h"
00022 #include "panels/Smoothiepanel.h"
00023 #include "panels/ReprapDiscountGLCD.h"
00024 #include "panels/ST7565.h"
00025 #include "version.h"
00026 
00027 #define panel_checksum             CHECKSUM("panel")
00028 #define enable_checksum            CHECKSUM("enable")
00029 #define lcd_checksum               CHECKSUM("lcd")
00030 #define i2c_lcd_checksum           CHECKSUM("i2c_lcd")
00031 #define viki_lcd_checksum          CHECKSUM("viki_lcd")
00032 #define smoothiepanel_checksum     CHECKSUM("smoothiepanel")
00033 #define panelolu2_checksum         CHECKSUM("panelolu2")
00034 #define rrd_glcd_checksum          CHECKSUM("reprap_discount_glcd")
00035 #define st7565_glcd_checksum       CHECKSUM("st7565_glcd")
00036 
00037 #define menu_offset_checksum        CHECKSUM("menu_offset")
00038 #define encoder_resolution_checksum CHECKSUM("encoder_resolution")
00039 #define jog_x_feedrate_checksum     CHECKSUM("alpha_jog_feedrate")
00040 #define jog_y_feedrate_checksum     CHECKSUM("beta_jog_feedrate")
00041 #define jog_z_feedrate_checksum     CHECKSUM("gamma_jog_feedrate")
00042 #define longpress_delay_checksum    CHECKSUM("longpress_delay")
00043 
00044 #define hotend_temp_checksum CHECKSUM("hotend_temperature")
00045 #define bed_temp_checksum    CHECKSUM("bed_temperature")
00046 
00047 Panel::Panel()
00048 {
00049     this->counter_changed = false;
00050     this->click_changed = false;
00051     this->refresh_flag = false;
00052     this->enter_menu_mode();
00053     this->lcd = NULL;
00054     this->do_buttons = false;
00055     this->idle_time = 0;
00056     this->start_up = true;
00057     this->current_screen = NULL;
00058     strcpy(this->playing_file, "Playing file");
00059 }
00060 
00061 Panel::~Panel()
00062 {
00063     delete this->lcd;
00064 }
00065 
00066 void Panel::on_module_loaded()
00067 {
00068     // Exit if this module is not enabled
00069     if ( !THEKERNEL->config->value( panel_checksum, enable_checksum )->by_default(false)->as_bool() ) {
00070         delete this;
00071         return;
00072     }
00073 
00074     // Initialise the LCD, see which LCD to use
00075     if (this->lcd != NULL) delete this->lcd;
00076     int lcd_cksm = get_checksum(THEKERNEL->config->value(panel_checksum, lcd_checksum)->by_default("i2c")->as_string());
00077 
00078     // Note checksums are not const expressions when in debug mode, so don't use switch
00079     if (lcd_cksm == i2c_lcd_checksum) {
00080         this->lcd = new I2CLCD();
00081     } else if (lcd_cksm == viki_lcd_checksum) {
00082         this->lcd = new VikiLCD();
00083         this->lcd->set_variant(0);
00084     } else if (lcd_cksm == panelolu2_checksum) {
00085         this->lcd = new VikiLCD();
00086         this->lcd->set_variant(1);
00087     } else if (lcd_cksm == smoothiepanel_checksum) {
00088         this->lcd = new Smoothiepanel();
00089     } else if (lcd_cksm == rrd_glcd_checksum) {
00090         this->lcd = new ReprapDiscountGLCD();
00091     } else if (lcd_cksm == st7565_glcd_checksum) {
00092         this->lcd = new ST7565();
00093     } else {
00094         // no lcd type defined
00095         return;
00096     }
00097 
00098     this->custom_screen= new CustomScreen(); // this needs to be called here as it needs the config cache loaded
00099 
00100     // some panels may need access to this global info
00101     this->lcd->setPanel(this);
00102 
00103     // the number of screen lines the panel supports
00104     this->screen_lines = this->lcd->get_screen_lines();
00105 
00106     // some encoders may need more clicks to move menu, this is a divisor and is in config as it is
00107     // an end user usability issue
00108     this->menu_offset = THEKERNEL->config->value( panel_checksum, menu_offset_checksum )->by_default(0)->as_number();
00109 
00110     // override default encoder resolution if needed
00111     this->encoder_click_resolution = THEKERNEL->config->value( panel_checksum, encoder_resolution_checksum )->by_default(this->lcd->getEncoderResolution())->as_number();
00112 
00113     // load jogging feedrates in mm/min
00114     jogging_speed_mm_min[0] = THEKERNEL->config->value( panel_checksum, jog_x_feedrate_checksum )->by_default(3000.0f)->as_number();
00115     jogging_speed_mm_min[1] = THEKERNEL->config->value( panel_checksum, jog_y_feedrate_checksum )->by_default(3000.0f)->as_number();
00116     jogging_speed_mm_min[2] = THEKERNEL->config->value( panel_checksum, jog_z_feedrate_checksum )->by_default(300.0f )->as_number();
00117 
00118     // load the default preset temeratures
00119     default_hotend_temperature = THEKERNEL->config->value( panel_checksum, hotend_temp_checksum )->by_default(185.0f )->as_number();
00120     default_bed_temperature    = THEKERNEL->config->value( panel_checksum, bed_temp_checksum    )->by_default(60.0f  )->as_number();
00121 
00122 
00123     this->up_button.up_attach(    this, &Panel::on_up );
00124     this->down_button.up_attach(  this, &Panel::on_down );
00125     this->click_button.up_attach( this, &Panel::on_select );
00126     this->back_button.up_attach(  this, &Panel::on_back );
00127     this->pause_button.up_attach( this, &Panel::on_pause );
00128 
00129 
00130     //setting longpress_delay
00131     int longpress_delay =  THEKERNEL->config->value( panel_checksum, longpress_delay_checksum )->by_default(0)->as_number();
00132     this->up_button.set_longpress_delay(longpress_delay);
00133     this->down_button.set_longpress_delay(longpress_delay);
00134 //    this->click_button.set_longpress_delay(longpress_delay);
00135 //    this->back_button.set_longpress_delay(longpress_delay);
00136 //    this->pause_button.set_longpress_delay(longpress_delay);
00137     
00138     
00139     THEKERNEL->slow_ticker->attach( 100,  this, &Panel::button_tick );
00140     THEKERNEL->slow_ticker->attach( 1000, this, &Panel::encoder_check );
00141 
00142     // Register for events
00143     this->register_for_event(ON_IDLE);
00144     this->register_for_event(ON_MAIN_LOOP);
00145     this->register_for_event(ON_GCODE_RECEIVED);
00146 
00147     // Refresh timer
00148     THEKERNEL->slow_ticker->attach( 20, this, &Panel::refresh_tick );
00149 }
00150 
00151 // Enter a screen, we only care about it now
00152 void Panel::enter_screen(PanelScreen *screen)
00153 {
00154     screen->panel = this;
00155     this->current_screen = screen;
00156     this->reset_counter();
00157     this->current_screen->on_enter();
00158 }
00159 
00160 // Reset the counter
00161 void Panel::reset_counter()
00162 {
00163     *this->counter = 0;
00164     this->counter_changed = false;
00165 }
00166 
00167 // Indicate the idle loop we want to call the refresh hook in the current screen
00168 // called 20 times a second
00169 uint32_t Panel::refresh_tick(uint32_t dummy)
00170 {
00171     this->refresh_flag = true;
00172     this->idle_time++;
00173     return 0;
00174 }
00175 
00176 // Encoder pins changed in interrupt
00177 uint32_t Panel::encoder_check(uint32_t dummy)
00178 {
00179     // TODO if encoder reads go through i2c like on smoothie panel this needs to be
00180     // optionally done in idle loop, however when reading encoder directly it needs to be done
00181     // frequently, smoothie panel will return an actual delta count so won't miss any if polled slowly
00182     // NOTE this code will not work if change is not -1,0,-1 anything greater (as in above case) will not work properly
00183     static int encoder_counter = 0;
00184     int change = lcd->readEncoderDelta();
00185     encoder_counter += change;
00186 
00187     if ( change != 0 && encoder_counter % this->encoder_click_resolution == 0 ) {
00188         this->counter_changed = true;
00189         (*this->counter) += change;
00190         this->idle_time = 0;
00191     }
00192     return 0;
00193 }
00194 
00195 // Read and update each button
00196 uint32_t Panel::button_tick(uint32_t dummy)
00197 {
00198     this->do_buttons = true;
00199     return 0;
00200 }
00201 
00202 void Panel::on_gcode_received(void *argument)
00203 {
00204     Gcode *gcode = static_cast<Gcode *>(argument);
00205     if ( gcode->has_m) {
00206         if ( gcode->m == 117 ) { // set LCD message
00207             this->message = get_arguments(gcode->command);
00208             if (this->message.size() > 20) this->message = this->message.substr(0, 20);
00209             gcode->mark_as_taken();
00210         }
00211     }
00212 }
00213 
00214 // on main loop, we can send gcodes or do anything that waits in this loop
00215 void Panel::on_main_loop(void *argument)
00216 {
00217     if (this->current_screen != NULL) {
00218         this->current_screen->on_main_loop();
00219         this->lcd->on_main_loop();
00220     }
00221 }
00222 
00223 
00224 #define ohw_logo_antipixel_width 80
00225 #define ohw_logo_antipixel_height 15
00226 static const uint8_t ohw_logo_antipixel_bits[] = {
00227     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00,
00228     0x00, 0x00, 0x00, 0x01, 0x80, 0x0C, 0x00, 0x33, 0x18, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x5E,
00229     0x80, 0x2D, 0x6B, 0x9B, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x18, 0xAB, 0xFF, 0xFF,
00230     0xFF, 0xFD, 0x80, 0xFF, 0xC0, 0x2D, 0x7B, 0xB3, 0xFF, 0xFF, 0xFF, 0xFD, 0x80, 0x7F, 0x80, 0x33,
00231     0x78, 0xBB, 0xFF, 0xFF, 0xFF, 0xFD, 0x81, 0xF3, 0xE0, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD,
00232     0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xB3, 0x18, 0xDD, 0x98, 0xC5, 0x81, 0xF3, 0xE0, 0x3F, 0xFD, 0xAD,
00233     0x6B, 0x5D, 0x6B, 0x5D, 0x80, 0x73, 0x80, 0x3F, 0xFC, 0x21, 0x1B, 0x55, 0x08, 0xC5, 0x80, 0xF3,
00234     0xC0, 0x3F, 0xFD, 0xAD, 0x5B, 0x49, 0x6A, 0xDD, 0x80, 0xE1, 0xC0, 0x3F, 0xFD, 0xAD, 0x68, 0xDD,
00235     0x6B, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF,
00236     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
00237 };
00238 
00239 // On idle things, we don't want to do shit in interrupts
00240 // don't queue gcodes in this
00241 void Panel::on_idle(void *argument)
00242 {
00243     if (this->start_up) {
00244         this->lcd->init();
00245 
00246         Version v;
00247         string build(v.get_build());
00248         string date(v.get_build_date());
00249         this->lcd->clear();
00250         this->lcd->setCursor(0, 0); this->lcd->printf("Welcome to Smoothie");
00251         this->lcd->setCursor(0, 1); this->lcd->printf("%s", build.substr(0, 20).c_str());
00252         this->lcd->setCursor(0, 2); this->lcd->printf("%s", date.substr(0, 20).c_str());
00253         this->lcd->setCursor(0, 3); this->lcd->printf("Please wait....");
00254 
00255         if (this->lcd->hasGraphics()) {
00256             this->lcd->bltGlyph(24, 40, ohw_logo_antipixel_width, ohw_logo_antipixel_height, ohw_logo_antipixel_bits);
00257         }
00258 
00259         this->lcd->on_refresh(true); // tell lcd to display now
00260 
00261         // Default top screen
00262         this->top_screen= new MainMenuScreen();
00263         this->top_screen->set_panel(this);
00264         this->custom_screen->set_parent(this->top_screen);
00265         this->start_up = false;
00266         return;
00267     }
00268 
00269     MainMenuScreen *mms= static_cast<MainMenuScreen*>(this->top_screen);
00270     // after being idle for a while switch to Watch screen
00271     if (this->current_screen != NULL && this->idle_time > this->current_screen->idle_timeout_secs()*20) {
00272         this->idle_time = 0;
00273         if (mms->watch_screen != this->current_screen) {
00274             this->enter_screen(mms->watch_screen);
00275             // TODO do we need to reset any state?
00276         }
00277 
00278         return;
00279     }
00280 
00281     if(current_screen == NULL && this->idle_time > 20*4) {
00282         this->enter_screen(mms->watch_screen);
00283         return;
00284     }
00285 
00286     if (this->do_buttons) {
00287         // we don't want to do I2C in interrupt mode
00288         this->do_buttons = false;
00289 
00290         // read the actual buttons
00291         int but = lcd->readButtons();
00292         if (but != 0) {
00293             this->idle_time = 0;
00294             if(current_screen == NULL) {
00295                 // we were in startup screen so go to watch screen
00296                 this->enter_screen(mms->watch_screen);
00297                 return;
00298             }
00299         }
00300 
00301         // fire events if the buttons are active and debounce is satisfied
00302         this->up_button.check_signal(but & BUTTON_UP);
00303         this->down_button.check_signal(but & BUTTON_DOWN);
00304         this->back_button.check_signal(but & BUTTON_LEFT);
00305         this->click_button.check_signal(but & BUTTON_SELECT);
00306         this->pause_button.check_signal(but & BUTTON_PAUSE);
00307     }
00308 
00309     // If we are in menu mode and the position has changed
00310     if ( this->mode == MENU_MODE && this->counter_change() ) {
00311         this->menu_update();
00312     }
00313 
00314     // If we are in control mode
00315     if ( this->mode == CONTROL_MODE && this->counter_change() ) {
00316         this->control_value_update();
00317     }
00318 
00319     // If we must refresh
00320     if ( this->refresh_flag ) {
00321         this->refresh_flag = false;
00322         if (this->current_screen != NULL) {
00323             this->current_screen->on_refresh();
00324             this->lcd->on_refresh();
00325         }
00326     }
00327 }
00328 
00329 // Hooks for button clicks
00330 uint32_t Panel::on_up(uint32_t dummy)
00331 {
00332     // this is simulating encoder clicks, but needs to be inverted to
00333     // increment values on up,increment by
00334     int inc = (this->mode == CONTROL_MODE) ? 1 : -(this->menu_offset+1);
00335     *this->counter += inc;
00336     this->counter_changed = true;
00337     return 0;
00338 }
00339 
00340 uint32_t Panel::on_down(uint32_t dummy)
00341 {
00342     int inc = (this->mode == CONTROL_MODE) ? -1 : (this->menu_offset+1);
00343     *this->counter += inc;
00344     this->counter_changed = true;
00345     return 0;
00346 }
00347 
00348 // on most menu screens will go back to previous higher menu
00349 uint32_t Panel::on_back(uint32_t dummy)
00350 {
00351     if (this->mode == MENU_MODE && this->current_screen != NULL && this->current_screen->parent != NULL) {
00352         this->enter_screen(this->current_screen->parent);
00353     }
00354     return 0;
00355 }
00356 
00357 uint32_t Panel::on_select(uint32_t dummy)
00358 {
00359     // TODO make configurable, including turning off
00360     // buzz is ignored on panels that do not support buzz
00361     this->click_changed = true;
00362     this->idle_time = 0;
00363     lcd->buzz(60, 300); // 50ms 300Hz
00364     return 0;
00365 }
00366 
00367 uint32_t Panel::on_pause(uint32_t dummy)
00368 {
00369     if (!paused) {
00370         THEKERNEL->pauser->take();
00371         paused = true;
00372     } else {
00373         THEKERNEL->pauser->release();
00374         paused = false;
00375     }
00376     return 0;
00377 }
00378 
00379 bool Panel::counter_change()
00380 {
00381     if ( this->counter_changed ) {
00382         this->counter_changed = false;
00383         return true;
00384     } else {
00385         return false;
00386     }
00387 }
00388 bool Panel::click()
00389 {
00390     if ( this->click_changed ) {
00391         this->click_changed = false;
00392         return true;
00393     } else {
00394         return false;
00395     }
00396 }
00397 
00398 
00399 // Enter menu mode
00400 void Panel::enter_menu_mode()
00401 {
00402     this->mode = MENU_MODE;
00403     this->counter = &this->menu_selected_line;
00404     this->menu_changed = false;
00405 }
00406 
00407 void Panel::setup_menu(uint16_t rows)
00408 {
00409     this->setup_menu(rows, min(rows, this->max_screen_lines()));
00410 }
00411 
00412 void Panel::setup_menu(uint16_t rows, uint16_t lines)
00413 {
00414     this->menu_selected_line = 0;
00415     this->menu_current_line = 0;
00416     this->menu_start_line = 0;
00417     this->menu_rows = rows;
00418     this->panel_lines = lines;
00419 }
00420 
00421 void Panel::menu_update()
00422 {
00423     // Limits, up and down
00424     // NOTE menu_selected_line is changed in an interrupt and can change at any time
00425     int msl = this->menu_selected_line; // hopefully this is atomic
00426 
00427     #if 0
00428     // this allows it to wrap but with new method we dont; want to wrap
00429     msl = msl % ( this->menu_rows << this->menu_offset );
00430     while ( msl < 0 ) {
00431         msl += this->menu_rows << this->menu_offset;
00432     }
00433     #else
00434         // limit selected line to screen lines available
00435         if(msl >= this->menu_rows<<this->menu_offset){
00436             msl= (this->menu_rows-1)<<this->menu_offset;
00437         }else if(msl < 0) msl= 0;
00438     #endif
00439 
00440     this->menu_selected_line = msl; // update atomically we hope
00441     // figure out which actual line to select, if we have a menu offset it means we want to move one line per two clicks
00442     if(msl % (this->menu_offset+1) == 0) { // only if divisible by offset
00443         this->menu_current_line = msl >> this->menu_offset;
00444     }
00445 
00446     // What to display
00447     if ( this->menu_rows > this->panel_lines ) {
00448         #if 0
00449         // old way of scrolling not nice....
00450         if ( this->menu_current_line >= 2 ) {
00451             this->menu_start_line = this->menu_current_line - 1;
00452         }
00453         if ( this->menu_current_line > this->menu_rows - this->panel_lines ) {
00454             this->menu_start_line = this->menu_rows - this->panel_lines;
00455         }
00456         #else
00457         // new way we only scroll the lines when the cursor hits the bottom of the screen or the top of the screen
00458         // do we want to scroll up?
00459         int sl= this->menu_current_line - this->menu_start_line; // screen line we are on
00460         if(sl >= this->panel_lines) {
00461             this->menu_start_line += ((sl+1)-this->panel_lines); // scroll up to keep it on the screen
00462 
00463         }else if(sl < 0 ) { // do we want to scroll down?
00464             this->menu_start_line += sl; // scroll down
00465         }
00466         #endif
00467 
00468     }else{
00469         this->menu_start_line = 0;
00470     }
00471 
00472     this->menu_changed = true;
00473 }
00474 
00475 bool Panel::menu_change()
00476 {
00477     if ( this->menu_changed ) {
00478         this->menu_changed = false;
00479         return true;
00480     } else {
00481         return false;
00482     }
00483 }
00484 
00485 bool Panel::control_value_change()
00486 {
00487     if ( this->control_value_changed ) {
00488         this->control_value_changed = false;
00489         return true;
00490     } else {
00491         return false;
00492     }
00493 }
00494 
00495 bool Panel::enter_control_mode(float passed_normal_increment, float passed_pressed_increment)
00496 {
00497     this->mode = CONTROL_MODE;
00498     this->normal_increment  = passed_normal_increment;
00499     this->counter = &this->control_normal_counter;
00500     this->control_normal_counter = 0;
00501     this->control_base_value = 0;
00502     return true;
00503 }
00504 
00505 void Panel::control_value_update()
00506 {
00507     // TODO what do we do here?
00508     this->control_value_changed = true;
00509 }
00510 
00511 void Panel::set_control_value(float value)
00512 {
00513     this->control_base_value = value;
00514 }
00515 
00516 float Panel::get_control_value()
00517 {
00518     return this->control_base_value + (this->control_normal_counter * this->normal_increment);
00519 }
00520 
00521 bool Panel::is_playing() const
00522 {
00523     void *returned_data;
00524 
00525     bool ok = THEKERNEL->public_data->get_value( player_checksum, is_playing_checksum, &returned_data );
00526     if (ok) {
00527         bool b = *static_cast<bool *>(returned_data);
00528         return b;
00529     }
00530     return false;
00531 }
00532 
00533 void  Panel::set_playing_file(string f)
00534 {
00535     // just copy the first 20 characters after the first / if there
00536     size_t n = f.find_last_of('/');
00537     if (n == string::npos) n = 0;
00538     strncpy(playing_file, f.substr(n + 1, 19).c_str(), sizeof(playing_file));
00539     playing_file[sizeof(playing_file) - 1] = 0;
00540 }