Welcome! Log In Create A New Profile

Advanced

RepRapDiscount Full Graphic LCD reset button code

Posted by DaveOB 
RepRapDiscount Full Graphic LCD reset button code
July 20, 2015 06:22AM
Can someone please point me to the place to change code in Marlin for the reset button of the RepRapDiscount Full Graphic Smart Controller.

I assume it is somewhere in the file dogm_lcd_implementation.h
or where the comms from the LCD are handled by Marlin_main.cpp or ultralcd.cpp ?

From what I understand, the current function of the button is to kill the heaters, motors and execution of the gcode file.

I would like to modify that so it instead goes into a 'filament change' routine or the same routine activated with the Pause option of the LCD menu.
A second press ( in the paused state ) should 'resume' the print.

That way, if I see a print problem, I can press the button to cause a pause in the printing, without having to fumble around the menu to stop / pause print.

If I still want to terminate the print job I can then press the reset button on the ramps board.

Regards
Re: RepRapDiscount Full Graphic LCD reset button code
July 21, 2015 04:44AM
The circuit shows it just grounds the cpu reset line. Ie hardware reset. Wich on power on will turn off heated bed and hotend etc.

Ie I dont think it works how you think it does.
Re: RepRapDiscount Full Graphic LCD reset button code
July 21, 2015 09:12AM
Quote
Dust
The circuit shows it just grounds the cpu reset line. Ie hardware reset. Wich on power on will turn off heated bed and hotend etc.

Ie I dont think it works how you think it does.

Many Thanks for that link. Now that makes sense.

I suppose next I should look to see if any of the Arduino pins are unused by the Ramps board or the LCD display, and then I can add a seperate button ( or buttons ) for Pause / resume, etc.
Re: RepRapDiscount Full Graphic LCD reset button code
July 21, 2015 01:17PM
That's not actually correct. It grounds the line errantly labelled as reset on the LCD schematic, but that line is wired to pin 41 on RAMPS, which is mapped in pins.h to whatever you want (default is KILL_PIN, IIRC), or can be disabled outright. It is *NOT* a dedicated reset line . . .

- Tim
Re: RepRapDiscount Full Graphic LCD reset button code
July 21, 2015 02:42PM
Quote
tadawson
That's not actually correct. It grounds the line errantly labelled as reset on the LCD schematic, but that line is wired to pin 41 on RAMPS, which is mapped in pins.h to whatever you want (default is KILL_PIN, IIRC), or can be disabled outright. It is *NOT* a dedicated reset line . . .

- Tim

Thank You Tim

If I have traced this correctly, then the Graphical LCD Button press is passed to :

Marlin_main.cpp
void manage_inactivity()


#if defined(KILL_PIN) && KILL_PIN > -1
    if( 0 == READ(KILL_PIN) )
      kill();
  #endif


and then :
void kill()
{
  cli(); // Stop interrupts
  disable_heater();

  disable_x();
  disable_y();
  disable_z();
  disable_e0();
  disable_e1();
  disable_e2();

#if defined(PS_ON_PIN) && PS_ON_PIN > -1
  pinMode(PS_ON_PIN,INPUT);
#endif
  SERIAL_ERROR_START;
  SERIAL_ERRORLNPGM(MSG_ERR_KILLED);
  LCD_ALERTMESSAGEPGM(MSG_KILLED);
  suicide();
  while(1) { /* Intentionally left empty */ } // Wait for reset
}

Would it then be correct, if I wanted to change that to a Pause with X = 0, y = 225 then :

I have assumed that I need to change the 'kill' function call - if I have correctly identified the correct location - and replace it with a new function to manage the pause.

By Pause, I mean that I want the X,Y to change, like it would for a filament change, and then resume if the button is again pressed.

The following code is a slim version of the filament change function.


#if defined(KILL_PIN) && KILL_PIN > -1
    if( 0 == READ(KILL_PIN) )
      ParkPause();
  #endif

void ParkPause()
{
        float target[4];
        float lastpos[4];
        target[X_AXIS]=current_position[X_AXIS];
        target[Y_AXIS]=current_position[Y_AXIS];
        target[Z_AXIS]=current_position[Z_AXIS];
        target[E_AXIS]=current_position[E_AXIS];
        lastpos[X_AXIS]=current_position[X_AXIS];
        lastpos[Y_AXIS]=current_position[Y_AXIS];
        lastpos[Z_AXIS]=current_position[Z_AXIS];
        lastpos[E_AXIS]=current_position[E_AXIS];

	target[X_AXIS]= 0;

	target[Y_AXIS]= 225;
		
        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);

        //finish moves
        st_synchronize();

        uint8_t cnt=0;
        while(!lcd_clicked()){
          cnt++;
          manage_heater();
          manage_inactivity();
          lcd_update();
        }

        //return to normal
        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //should do nothing
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move xy back
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move z back
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], lastpos[E_AXIS], feedrate/60, active_extruder); //final untretract
}

Edited 1 time(s). Last edit at 07/21/2015 02:50PM by DaveOB.
Re: RepRapDiscount Full Graphic LCD reset button code
July 21, 2015 03:33PM
Were it me, I would define a new pinning and function - IE FILSWAP_PIN or some such, map that to pin 41 in pins.h, and then clone and rename the kill function, and then could do either, were I to decide to add another switch . . . The connection between the switch and the function is soft, so why bugger up good existing code?

IE:

pins.h:
#if defined(REPRAP_DISCOUNT_SMART_CONTROLLER) || defined(G3D_PANEL)
// #define KILL_PIN 41
#define KILL_PIN -1
#define FILSWAP_PIN 41
#else
#define KILL_PIN -1
#endif

*and*

#if defined(FILSWAP_PIN) && FILSWAP_PIN > -1
if( 0 == READ(FILSWAP_PIN) )
ParkPause();
#endif

.
. Your function . . .
.



- Tim

Edited 1 time(s). Last edit at 07/21/2015 03:36PM by tadawson.
Re: RepRapDiscount Full Graphic LCD reset button code
July 21, 2015 03:41PM
Quote
tadawson
Were it me, I would define a new pinning and function - IE FILSWAP_PIN or some such, map that to pin 41 in pins.h, and then clone and rename the kill function, and then could do either, were I to decide to add another switch . . . The connection between the switch and the function is soft, so why bugger up good existing code?

IE:

pins.h:
#if defined(REPRAP_DISCOUNT_SMART_CONTROLLER) || defined(G3D_PANEL)
// #define KILL_PIN 41
#define KILL_PIN -1
#define FILSWAP_PIN 41
#else
#define KILL_PIN -1
#endif

*and*

#if defined(FILSWAP_PIN) && FILSWAP_PIN > -1
if( 0 == READ(FILSWAP_PIN) )
ParkPause();
#endif

.
. Your function . . .
.



- Tim

Thanks again Tim

Sorry, but with my limited coding, I don't see the difference between ( IF I understand your suggestion correct ) :

A. changing the pin mapping in pins.h from KILL_PIN to FILSWAP_PIN
or
B changing the function that gets called from
if( 0 == READ(KILL_PIN) )
      kill();

to
if( 0 == READ(KILL_PIN) )
      ParkPause();

In either case, there is a need for a new function ( ParkPause ).
Re: RepRapDiscount Full Graphic LCD reset button code
July 23, 2015 05:35AM
pin 41! WHO LABLES THAT RESET!!!

Thanks for the correction. noted for future reference.
Re: RepRapDiscount Full Graphic LCD reset button code
July 23, 2015 02:29PM
Quote
DaveOB
Quote
tadawson
Were it me, I would define a new pinning and function - IE FILSWAP_PIN or some such, map that to pin 41 in pins.h, and then clone and rename the kill function, and then could do either, were I to decide to add another switch . . . The connection between the switch and the function is soft, so why bugger up good existing code?

IE:

pins.h:
#if defined(REPRAP_DISCOUNT_SMART_CONTROLLER) || defined(G3D_PANEL)
// #define KILL_PIN 41
#define KILL_PIN -1
#define FILSWAP_PIN 41
#else
#define KILL_PIN -1
#endif

*and*

#if defined(FILSWAP_PIN) && FILSWAP_PIN > -1
if( 0 == READ(FILSWAP_PIN) )
ParkPause();
#endif

.
. Your function . . .
.



- Tim

Thanks again Tim

Sorry, but with my limited coding, I don't see the difference between ( IF I understand your suggestion correct ) :

A. changing the pin mapping in pins.h from KILL_PIN to FILSWAP_PIN
or
B changing the function that gets called from
if( 0 == READ(KILL_PIN) )
      kill();

to
if( 0 == READ(KILL_PIN) )
      ParkPause();

In either case, there is a need for a new function ( ParkPause ).

Functionally, there is no difference . . . in reality, my way allows both functions to be intact in the code, such that a second switch could be added allowing both kill (emergency stop) as well as filament swap; yours destroys any possibility to use kill.

- Tim
Re: RepRapDiscount Full Graphic LCD reset button code
July 24, 2015 04:19PM
Sounds like a great use for that otherwise useless button. Any luck yet?

Edited 1 time(s). Last edit at 07/24/2015 04:20PM by AquaticsLive.
Re: RepRapDiscount Full Graphic LCD reset button code
July 24, 2015 05:12PM
Quote
AquaticsLive
Sounds like a great use for that otherwise useless button. Any luck yet?

Have not got round to trying it yet - should do over the weekend and will report back here.
Re: RepRapDiscount Full Graphic LCD reset button code
July 24, 2015 08:49PM
I don't know about you guys, but I find that an emergency stop is a lot of things, but useless isn't one of them . . . And I'd rather have it that some other function that could more easily implemented as a macro in Pronterface . . . such as filament load and unload . . .

- Tim
Re: RepRapDiscount Full Graphic LCD reset button code
July 25, 2015 04:41AM
Quote
tadawson
I don't know about you guys, but I find that an emergency stop is a lot of things, but useless isn't one of them . . . And I'd rather have it that some other function that could more easily implemented as a macro in Pronterface . . . such as filament load and unload . . .

- Tim

Hi Tim

I hear your point, but my printer runs from the SD card on the graphical LCD unit, and is not connected to my PC.

My thinking is to implement the ParkPause instead of the Kill function.

That way, if I see a problem or have a concern, I can press the LCD button to ParkPause ( X = 0, Y = 225 in my case to print the print forward for inspection ) and if the problem is serious, or am wanting to abort the print, then I press the reset button.

If I am happy with the print and want to continue, press the button again to resume print. Start with X and Y Home, then resume.

ParkPause does not need to have the filament change functions built in for my printer, as the filament can be changed without using the E motor.
Using this modification : http://www.thingiverse.com/thing:678089

Edited 1 time(s). Last edit at 07/25/2015 04:44AM by DaveOB.
Re: RepRapDiscount Full Graphic LCD reset button code
July 25, 2015 06:23AM
OK. So I am busy changing code, but am stuck on a small issue ..

In the new PausePark function, the code remembers the current position, changes X to 0, Y to 225.

Then it waits for another button press to continue.

The ideal step to take next, I think, would be to Home the X and Y axis ( in case they were accidentally moved during the pause ), before returning to the original positions to resume print.

Any pointers or sample code on what function to call to home the X and Y axis ?
I know it can be done from g-Code read from the SD file, but in this case that would not help, as we do not know at which point in the file the PausePark was activated.

So in the ParkPause function, I need to add something in to cause the home sequence :

	uint8_t cnt=0;
	while(!lcd_clicked()){  // wait for the LCD button to be pressed again
		cnt++;
		manage_heater();
		manage_inactivity();
		lcd_update();
	}

	
	
	// go back to Home positions for X and Y in case they were moved from 0,225

	
	
	//return to normal
	plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //should do nothing
	plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move xy back
	plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move z back
	plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], lastpos[E_AXIS], feedrate/60, active_extruder); //final untretract

If anyone could contribute the code I need, I can get this tested and then add a full step-by-step of the code.

Thanks
Dave
Re: RepRapDiscount Full Graphic LCD reset button code
July 27, 2015 05:25AM
OK. So I am making progress, but slow.

Stuck on an issue with moving the axis while paused.

I seem to be able to pause the printing, and then move the axis using the plan_buffer_line code below.

		card.pauseSDPrint();
		plan_buffer_line(ParkPausetarget[X_AXIS], ParkPausetarget[Y_AXIS], ParkPausetarget[Z_AXIS], ParkPauselastpos[E_AXIS], 150, active_extruder);
		st_synchronize();

My issue seems to be when I press the button a second time to resume the printing and the function executes this code :
		MYSERIAL.println("Resume Normal Operation");
		plan_buffer_line(ParkPauselastpos[X_AXIS], ParkPauselastpos[Y_AXIS], ParkPauselastpos[Z_AXIS], ParkPauselastpos[E_AXIS], 150, active_extruder); //final untretract
		st_synchronize();
		card.startFileprint();

the axis are moving back to the pre-paused position, but directly after that are moving again about half way to the ParkPause position.

My very limited understanding is that the plan_buffer_line coordinates get added to the buffer that was read from the SD card, and I do not have any way of knowing what data, if any, exists in that buffer when I press the button to activate the ParkPause.

So my need now is to find a way to directly move the XYZ axis with code, without injecting that data in to the SD card read buffer.

Basically I am wanting to get to :

Printing busy
Press the Graphical LCD button ( the existing kill button )
Printing Pauses
Axis move to XYZ 5, 225, currentZ + 3
Waits for second button press

On second button press :
return XYZ to original positions
continue with the printing from the SD card buffer.
SOLVED :RepRapDiscount Full Graphic LCD reset button code
July 28, 2015 07:37AM
I now have ParkPause working and responding as I need it.

During printing from an SD file, I can press the Graphical LCD button ( was the 'kill' button ) and that will start the ParkPause process.

Z axis lifts 5mm, X moves to 5, Y moves to 225 ( so extruder up, off to the side, and print bed forward for inspection ).

Printer then waits for the rotary encoder to be pressed to resume printing.

X and Y return to the pre-paused position, Z returns, and then printing starts.

Depending on your printer configuration, you may want to adjust the 5, 225 values in the code.

Disclaimer - I am not a coder or an expert on Marlin. I have developed this, with the very kind guidance of Pim Rutgers, solely for my own use. Use this code at your own risk and make sure you have a backup of your Marlin code before you start.


in Configuration.h
add the following to the file :

// ==> ParkPause : uncomment this line to change the graphical LCD button funtion from 'kill' to 'ParkPause'
#define EnableParkPause

in Marlin.h

change
void kill();
to
void kill();
// add this line for ParkPause
void ParkPause();


in Marlin_main.cpp
just before line :
float homing_feedrate[] = HOMING_FEEDRATE;
add
long ParkPauseMillis = -1; // prevents ParkPause from running unless printing has started

after line :
case 1: // G1
add
if(ParkPauseMillis == -1) ParkPauseMillis = 0; // enables the ParkPause option after printing has started

before the code :
#ifdef DUAL_X_CARRIAGE
case 605: // Set dual x-carriage movement mode:

add the new M code :
    case 6001: //custom ParkPause
    // called from ParkPause function using : enquecommand_P(PSTR("M6001"));
    {
        LCD_MESSAGEPGM("ParkPause Init");
        float target[4];
        float lastpos[4];
        target[X_AXIS]=current_position[X_AXIS];
        target[Y_AXIS]=current_position[Y_AXIS];
        target[Z_AXIS]=current_position[Z_AXIS];
        target[E_AXIS]=current_position[E_AXIS];
        lastpos[X_AXIS]=current_position[X_AXIS];
        lastpos[Y_AXIS]=current_position[Y_AXIS];
        lastpos[Z_AXIS]=current_position[Z_AXIS];
        lastpos[E_AXIS]=current_position[E_AXIS];

        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);

	target[Z_AXIS]+= 5;        //lift Z by 5mm
        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);
        st_synchronize();

        target[X_AXIS]= 5;
        target[Y_AXIS]= 225;
        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);
        st_synchronize();

        LCD_MESSAGEPGM("ParkPause Done");
        delay(100);
        while(!lcd_clicked()){
          manage_heater();
          manage_inactivity();
          lcd_update();
        }

        LCD_MESSAGEPGM("ParkPause Reset");
        lcd_update();
        ParkPauseMillis = 0;

        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //should do nothing
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move xy back
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move z back
        LCD_MESSAGEPGM("Resumed");
    }
    break;

change :
void kill()
{
		cli(); // Stop interrupts
		disable_heater();

		disable_x();
		disable_y();
		disable_z();
		disable_e0();
		disable_e1();
		disable_e2();

		#if defined(PS_ON_PIN) && PS_ON_PIN > -1
			pinMode(PS_ON_PIN,INPUT);
		#endif
		SERIAL_ERROR_START;
		SERIAL_ERRORLNPGM(MSG_ERR_KILLED);
		LCD_ALERTMESSAGEPGM(MSG_KILLED);
		suicide();
		while(1) { /* Intentionally left empty */ } // Wait for reset
}

to
void kill()
{
	#ifdef EnableParkPause  // check if the config option for ParkPause is enabled
		ParkPause();
	#else                            // otherwise do the normal kill sequence
		cli(); // Stop interrupts
		disable_heater();
		disable_x();
		disable_y();
		disable_z();
		disable_e0();
		disable_e1();
		disable_e2();
		#if defined(PS_ON_PIN) && PS_ON_PIN > -1
			pinMode(PS_ON_PIN,INPUT);
		#endif
		SERIAL_ERROR_START;
		SERIAL_ERRORLNPGM(MSG_ERR_KILLED);
		LCD_ALERTMESSAGEPGM(MSG_KILLED);
		suicide();
		while(1) { /* Intentionally left empty */ } // Wait for reset
	#endif
}

before :
void Stop()

add
// ParkPause - add this function for the ParkPause
void ParkPause()
{
	// if the button was pressed sometime in the last 5 seconds, ignore this button press as it is likely to be part of the first button press.
	if(ParkPauseMillis > 1){					
		if(millis() <= (ParkPauseMillis + 3000)){
			MYSERIAL.println("------------------------------------------------- ParkPause Aborted - less than 10 seconds since last press");					
			return;  // exit out of tis function and ignore the rest of the code in the function
		}
	}
	
	// otherwise if the button has not been pressed before, then ParkPause the printer
	if(ParkPauseMillis == 0){

		ParkPauseMillis = millis();
		enquecommand_P(PSTR("M6001"));
		return;  // exit out of tis function and ignore the rest of the code in the function

	}
		
}

Edited 1 time(s). Last edit at 07/28/2015 05:54PM by DaveOB.
Re: SOLVED :RepRapDiscount Full Graphic LCD reset button code
August 22, 2015 09:21PM
Is this pause/resume function working for everyone? Seems a perfect use for that button.
Re: SOLVED :RepRapDiscount Full Graphic LCD reset button code
August 23, 2015 12:06AM
Myself, I still find e-stop to be the perfect purpose . . . The other could be on a menu, it's not time critical to avoid potential damage. Or add another button - there are free Arduino pins available, and the same code will work, and have both.

- Tim
Re: SOLVED :RepRapDiscount Full Graphic LCD reset button code
August 23, 2015 02:10AM
Quote
RRuser
Is this pause/resume function working for everyone? Seems a perfect use for that button.

Hi
Is working perfectly in my printer and I have used it a few times. Have not seen any feedback if anyone else has tried it. As tadawson says, you could also add another seperate button and assign the code to a different pin number. You would then only need to modify the above code to watch the input pin state of the new pin, instead of re-directing the kill button push.

The only other comment that I have seen on the github feature request, is that if you first move the Z axis, then it would create a small peak on the plastic which could get knocked by the nozzle when print is resumed, whereas a faster sideways movement would be preferred so the height of the layer would not be affected. For this case, you would change the case 6001: code to :

case 6001: //custom ParkPause
    // called from ParkPause function using : enquecommand_P(PSTR("M6001"));
    {
        LCD_MESSAGEPGM("ParkPause Init");
        float target[4];
        float lastpos[4];
        target[X_AXIS]=current_position[X_AXIS];
        target[Y_AXIS]=current_position[Y_AXIS];
        target[Z_AXIS]=current_position[Z_AXIS];
        target[E_AXIS]=current_position[E_AXIS];
        lastpos[X_AXIS]=current_position[X_AXIS];
        lastpos[Y_AXIS]=current_position[Y_AXIS];
        lastpos[Z_AXIS]=current_position[Z_AXIS];
        lastpos[E_AXIS]=current_position[E_AXIS];

        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);

        target[X_AXIS]= 5;
        target[Y_AXIS]= 225;
        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);
        st_synchronize();

	target[Z_AXIS]+= 5;        //lift Z by 5mm
        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder);
        st_synchronize();

        LCD_MESSAGEPGM("ParkPause Done");
        delay(100);
        while(!lcd_clicked()){
          manage_heater();
          manage_inactivity();
          lcd_update();
        }

        LCD_MESSAGEPGM("ParkPause Reset");
        lcd_update();
        ParkPauseMillis = 0;

        plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //should do nothing
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], target[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move xy back
        plan_buffer_line(lastpos[X_AXIS], lastpos[Y_AXIS], lastpos[Z_AXIS], target[E_AXIS], feedrate/60, active_extruder); //move z back
        LCD_MESSAGEPGM("Resumed");
    }
    break;
Re: RepRapDiscount Full Graphic LCD reset button code
September 07, 2015 03:58PM
Hi Dave, have you considered submitting these code changes to the Marlin GitHUB? I think it would be a good idea to pass it on to them.
Re: RepRapDiscount Full Graphic LCD reset button code
September 07, 2015 04:14PM
Quote
atunguyd
Hi Dave, have you considered submitting these code changes to the Marlin GitHUB? I think it would be a good idea to pass it on to them.

Hi.

Yes, although I wouldn't have a clue about how to do it, someone else did post about it with a link back to here on this page :
https://github.com/MarlinFirmware/MarlinDev/issues/58

and I see it is also listed on :
https://github.com/MarlinFirmware/MarlinDev/labels/Feature%20Request

I have no idea if that means it will get any attention, but at least the idea is out there.

Regards
Dave
Sorry, only registered users may post in this forum.

Click here to login