Got my controller,

New members questions
User avatar
lnevo
Posts: 5422
Joined: Fri Jul 20, 2012 9:42 am

Re: Got my controller,

Post by lnevo »

Ok, more bugs fixed with the acclimation timer and final changes from SunLocation incorporated. Also added a display for the acclimation counter if its active. Some more comments and cleanup.

Code: Select all

#include <ReefAngel_Features.h>
#include <Globals.h>
#include <RA_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <InternalEEPROM.h>
#include <RA_NokiaLCD.h>
#include <RA_ATO.h>
#include <RA_Joystick.h>
#include <LED.h>
#include <RA_TempSensor.h>
#include <Relay.h>
#include <RA_PWM.h>
#include <Timer.h>
#include <Memory.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <RF.h>
#include <ReefAngel.h>
#include <SunLocation.h>
#include <WaterLevel.h>

////// Place global variable code below here

// Custom Menu Code
#include <avr/pgmspace.h>
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "Vortech Mode";
prog_char menu4_label[] PROGMEM = "Refugium Light";
prog_char menu5_label[] PROGMEM = "ATO Clear";
prog_char menu6_label[] PROGMEM = "Overheat Clear";
prog_char menu7_label[] PROGMEM = "PH Calibration";
prog_char menu8_label[] PROGMEM = "WLS Calibration";
prog_char menu9_label[] PROGMEM = "Date / Time";

// Group the menu entries together
PROGMEM const char *menu_items[] = {
menu1_label, menu2_label, menu3_label,
menu4_label, menu5_label, menu6_label,
menu7_label, menu8_label, menu9_label
};

// Vortech Defaults
byte vtPrevMode=0;
byte vtPrevSpeed=0;
byte vtPrevDuration=0;
// Default Mode
byte vtMode=Random2;
byte vtSpeed=45;
byte vtDuration=5;
// NTM mode
byte vtNTMSpeed=65;
byte vtNTMDuration=5;
// Night Mode
byte vtNightSpeed=20;
byte vtNightDuration=10;

boolean isNight=false;
boolean isFeeding=false;
boolean feedDelay=false;
boolean vtOverride=false;
boolean floatHigh=true;
boolean powerOutage=true;

SunLocation sl;
byte acclDay=0;
byte vacationMode=0;

//Define Custom Memory Location
#define Mem_B_RefillATO   100
#define Mem_B_ClearATO    101
#define Mem_B_Vacation    102
#define Mem_B_AcclDay     103

#define Var_HighATO    0
#define Var_Power      1
#define Var_Vacation   2
#define Var_AcclDay    3

//Define Relay Ports by Name
#define Return             1
#define Skimmer            2
#define WhiteLEDs          3
#define BlueLEDs           4
#define Extension          5
#define Heater             6
#define Refugium           7
#define Reactor            8

#define Unused1            Box1_Port1
#define Unused2            Box1_Port2
#define Vortech1           Box1_Port3
#define Vortech2           Box1_Port4
#define VortechUPS         Box1_Port5
#define Unused3            Box1_Port6
#define DPump1             Box1_Port7
#define DPump2             Box1_Port8

////// Place global variable code above here

// Setup on controller startup/reset
void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    // Initialize Menu
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));

    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = Port1Bit | Port2Bit | Port8Bit;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit | Port6Bit | Port8Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = Port3Bit | Port4Bit ;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port3Bit | Port4Bit | Port6Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;
    
    // Ports that default on
    ReefAngel.Relay.On(Return);
    ReefAngel.Relay.On(Vortech1);
    ReefAngel.Relay.On(Vortech2);
    ReefAngel.Relay.On(VortechUPS);
    // Ports that default off
    ReefAngel.Relay.Off(Extension);
    ReefAngel.Relay.Off(Unused1);
    ReefAngel.Relay.Off(Unused2);
    ReefAngel.Relay.Off(Unused3);
    
    ////// Place additional initialization code below here

    // What was our previous modes before we restarted?
    vtPrevMode=InternalMemory.RFMode_read();
    vtPrevSpeed=InternalMemory.RFSpeed_read();
    vtPrevDuration=InternalMemory.RFDuration_read();
    
    // Dummy CustomVar to activate portal feature
    ReefAngel.CustomVar[7]=255;
    
    ////// Place additional initialization code above here
}

void loop()
{
  // Default port modes. Use Memory settings for external control
  ReefAngel.StandardHeater(Heater);
  ReefAngel.DosingPumpRepeat1(DPump1);
  ReefAngel.DosingPumpRepeat2(DPump2);
  ReefAngel.StandardLights(WhiteLEDs);
  ReefAngel.ActinicLights(BlueLEDs);
    
  ////// Place your custom code below here
 
  // See if power is back on so DelayedOn ports will reset.
  if (powerOutage && ReefAngel.Relay.IsRelayPresent(EXP1_RELAY))
  {
    powerOutage=false;
    LastStart=now();
    ReefAngel.CustomVar[Var_Power]=0;
  }
  ReefAngel.Relay.DelayedOn(Skimmer); 
  ReefAngel.Relay.DelayedOn(Reactor); 

  // Find out if we are on vacation
  ReefAngel.CustomVar[Var_Vacation]=InternalMemory.read(Mem_B_Vacation);
  vacationMode=ReefAngel.CustomVar[Var_Vacation];
  
  // We're on vacation! Use external pump to top off the top off.    
  if (vacationMode==1) {
    ReefAngel.WaterLevelATO(Extension,30,61,63);
  } else {
    ReefAngel.Relay.Off(Extension);
  }
  
  // Real ATO Clear
  if (InternalMemory.read(Mem_B_ClearATO)==1) {
    ReefAngel.ATOClear();
    InternalMemory.write(Mem_B_ClearATO, 0);
  }  
  
  // ATO Refill mode. Top off ATO reservoir until it's at 100%    
  if (InternalMemory.read(Mem_B_RefillATO)==1) {
     if (ReefAngel.WaterLevel.GetLevel()<=100) {
       ReefAngel.Relay.On(Extension);
     } else {
       ReefAngel.Relay.Off(Extension);
       InternalMemory.write(Mem_B_RefillATO, 0);
     }
  }
  
  // See if we are acclimating corals and decrement the countdown each night
  static boolean acclCounterReady=false;
  if (now()%SECS_PER_DAY!=0) acclCounterReady=true;
  
  acclDay=InternalMemory.read(Mem_B_AcclDay);
  ReefAngel.CustomVar[Var_AcclDay]=acclDay;
  if (acclDay > 0) { 
    if (acclCounterReady && now()%SECS_PER_DAY==0) {
      acclDay--;
      acclCounterReady=false;
      InternalMemory.write(Mem_B_AcclDay,acclDay);
    }
  }  
      
  // -9 hour difference for time zone. 472/506 seconds were calculation corrections
  // The acclDay will adjust the sunrise/sunset if we are adjusting for new coral
  sl.SetOffset(-9,472+(acclDay*240),-9,506-(acclDay*120)); 
  
  // Calculate the new Sunrise / Sunset based on our GPS coordinates
  sl.CheckAndUpdate();
              
  // Let's make the time one number so we can figure out it's night or day...
  int currTime=NumMins(hour(),minute());
  int riseTime=NumMins(sl.GetRiseHour(),sl.GetRiseMinute()); 
  int setTime=NumMins(sl.GetSetHour(),sl.GetSetMinute());
  if ( (currTime <= riseTime) || (currTime >= setTime) ) isNight=true; else isNight=false;
    
  if (isNight) 
  {
    if ((hour()>=3) && (hour()<=4)) {
      // Some complete darkness
      ReefAngel.PWM.SetDaylight(0);
      ReefAngel.PWM.SetActinic(0);
    } else {
      // Set moonlights to the MoonPhase
      ReefAngel.PWM.SetDaylight(MoonPhase());
      ReefAngel.PWM.SetActinic(MoonPhase());
    }  
      
    // Vortech's to night mode.
    if (vtOverride==false && isFeeding==false) {
      setRFmode(Night,vtNightSpeed,vtNightDuration);
    }
    
    // Turn on refugium light opposite normal lights
    ReefAngel.Relay.On(Refugium);
  
  } else { // It's Daytime!
    // Regular lights are controlled by Memory variables set in SunLocation class
    ReefAngel.PWM.SetDaylight(0);
    ReefAngel.PWM.SetActinic(0);
    ReefAngel.Relay.Off(Refugium);
      
    // Go back to our regularly scheduled program.
    if (vtOverride==false && vtMode==Night) {
      setRFmode(vtPrevMode,vtPrevSpeed,vtPrevDuration);
    }
  }   
    
  // Enable Feeding Mode flag
  if (ReefAngel.DisplayedMenu==FEEDING_MODE) isFeeding=true;
  // Turn on Refugium light during water change mode
  if (ReefAngel.DisplayedMenu==WATERCHANGE_MODE) ReefAngel.Relay.On(Refugium);
  // Enable ATOHigh flag on purpose during feed/water change mode so we don't get alerts.
  if (ReefAngel.DisplayedMenu==FEEDING_MODE || ReefAngel.DisplayedMenu==WATERCHANGE_MODE) floatHigh=true;
    
  // Here's what we do if we're just out of feeding mode...
  if (ReefAngel.DisplayedMenu==DEFAULT_MENU && isFeeding) {
    isFeeding=false; 
    feedDelay=true; // This will let us know we want some extra time before Smart_NTM
    setRFtimer(30); // Start Smart_NTM in 30 minutes...
  } else if (vtOverride && ReefAngel.Timer[1].IsTriggered()) { // Our RF timer is over. 
    vtOverride=false; // Stop overriding the default RF mode

    // First let's deal with that extra 30 minutes
    if(feedDelay) {
      feedDelay=false; // Reset the feedDelay flag
      setRFmode(Smart_NTM,vtNTMSpeed,vtNTMDuration); // Smart_NTM time!
    } else {
      // Otherwise go to Previous settings
      setRFmode(vtPrevMode,vtPrevSpeed,vtPrevDuration); 
    }
  } else   {
    setRFmode(); // Update the mode if we change it remotely
  }
  // A little extra Smart_NTM never hurt anyone
  if ( ((hour() == 15) && (minute() == 00) && (second() == 00) ) ) {
    setRFmode(Smart_NTM,vtNTMSpeed,vtNTMDuration);
  }
    
  // Turn off return pump if we run out of water!
  if (ReefAngel.LowATO.IsActive()) {
    bitClear(ReefAngel.Relay.RelayMaskOff,Return-1);
  } 
 
  // Turn off return if we are somehow overflowing the sump
  if (ReefAngel.HighATO.IsActive()) {
    if (!floatHigh) {
      floatHigh=true;
      ReefAngel.CustomVar[Var_HighATO]=1;
      bitClear(ReefAngel.Relay.RelayMaskOff,Return-1);
    }  
  } else {
    ReefAngel.CustomVar[Var_HighATO]=0;
    floatHigh=false;
  }  
      
  // Turn off Skimmer if Return pump is shutoff.   
  if (bitRead(ReefAngel.Relay.RelayMaskOff,Return-1)==0) {
    bitClear(ReefAngel.Relay.RelayMaskOff,Skimmer-1);
  }
    
  // Power Outage - Only Return Pump should be active
  if (!ReefAngel.Relay.IsRelayPresent(EXP1_RELAY)) // Expansion Relay NOT present
  {
    powerOutage=true;
    ReefAngel.Relay.Off (Skimmer); 
    ReefAngel.Relay.Off (WhiteLEDs); 
    ReefAngel.Relay.Off (BlueLEDs); 
    ReefAngel.Relay.Off (Extension);
    ReefAngel.Relay.Off (Heater);
    ReefAngel.Relay.Off (Refugium); 
    ReefAngel.Relay.Off (Reactor); 
    ReefAngel.CustomVar[Var_Power]=1;
  }
    
  ////// Place your custom code above here

  // This should always be the last line
  ReefAngel.Portal("lnevo");
  ReefAngel.ShowInterface();
}

// Vortech Helper functions
void setRFmode(int mode, int speed, int duration) {

  // Check if mode has changed
  if (mode!=vtMode) {  
    vtPrevMode=vtMode;
    vtMode=mode; 
  
    if (mode!=InternalMemory.RFMode_read()) { 
      InternalMemory.RFMode_write(mode); 
    }
    
    // Fix for coming out of night mode
    if (vtPrevMode==Night) {
      ReefAngel.RF.UseMemory=false;
      ReefAngel.RF.SetMode(Feeding_Stop,0,0);
      ReefAngel.RF.UseMemory=true;
    }
    
    // If it's at night or we are setting NTM, do this only temporarily
    if ( (isNight && vtMode != Night) || vtMode==Smart_NTM) {
      setRFtimer(60);
    }
  } 

  // Check if speed has changed
  if (speed!=vtSpeed) {  
    vtPrevSpeed=vtSpeed;
    vtSpeed=speed;
    
    if (speed!=InternalMemory.RFSpeed_read()) {
      InternalMemory.RFSpeed_write(speed);
    } 
  }

  // Check if duration has changed
  if (duration!=vtDuration) {  
    vtPrevDuration=vtDuration;
    vtDuration=duration; 
    
    if (speed!=InternalMemory.RFSpeed_read()) {
      InternalMemory.RFSpeed_write(speed);
    }
  }
}
void setRFmode() {
  setRFmode(InternalMemory.RFMode_read(), InternalMemory.RFSpeed_read(), InternalMemory.RFDuration_read());
}
void setRFtimer(int minutes) {
  ReefAngel.Timer[1].SetInterval(minutes*60);
  ReefAngel.Timer[1].Start();
  vtOverride=true;
}

// Menu Code
void MenuEntry1() {
  ReefAngel.FeedingModeStart();
}
void MenuEntry2() {
  ReefAngel.WaterChangeModeStart();
}
void MenuEntry3() {
  byte mode,speed,duration;
  byte prev_mode,prev_speed,prev_dur;
  
  mode=vtMode;
  mode++;
  
  prev_mode=vtPrevMode; prev_speed=vtPrevSpeed; prev_dur=vtPrevDuration;
  
  if (mode > 9) { 
    mode=0; 
    speed=50; duration=0; // Constant
  } else if (mode == 1) { 
    speed=40; duration=0; // Lagoon
  } else if (mode == 2) { 
    speed=45; duration=0; // Reef Crest
  } else if (mode == 3) {  
    speed=55; duration=10; // Short Pulse
  } else if (mode == 4) {
    speed=55; duration=20; // Long Pulse
  } else if (mode == 6) {
    speed=50; duration=10; // Smart_TSM
  } else if (mode == 5) {
    speed=vtNTMSpeed; duration=vtNTMDuration; // Smart_NTM
  } else if (mode == 7) {
    speed=vtNightSpeed; duration=vtNightDuration; // Night
    mode=9; 
  }  

  // Backup the previous modes. We don't want Night to become default...
  prev_mode=vtPrevMode; prev_speed=vtPrevSpeed; prev_dur=vtPrevDuration;
  setRFmode(mode,speed,duration);
  
  // If it's night time, don't overwrite the default daytime mode when using the menu
  if (!isNight) {
    vtPrevMode=prev_mode; vtPrevSpeed=prev_speed; vtPrevDuration=prev_dur;
  }
  
  ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;   
}
void MenuEntry4() {
  // Toggle the refugium light if we choose this menu entry.
  // Behavior is opposite for night vs day.
  
  if (isNight) { // Turn off the Refugium light
    if (bitRead(ReefAngel.Relay.RelayMaskOff,Refugium-1)==1) {
      bitClear(ReefAngel.Relay.RelayMaskOff,Refugium-1);
    } else {
      bitSet(ReefAngel.Relay.RelayMaskOff,Refugium-1);
    }
  } else { // Turn on the Refugium light
    if (bitRead(ReefAngel.Relay.RelayMaskOn,Refugium-1)==1) {
      bitClear(ReefAngel.Relay.RelayMaskOn,Refugium-1);
    } else {
      bitSet(ReefAngel.Relay.RelayMaskOn,Refugium-1);
    }
  }
  ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;
}
void MenuEntry5() {
  ReefAngel.ATOClear();
  ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry6() {
  ReefAngel.OverheatClear();
  ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry7() {
  ReefAngel.SetupCalibratePH();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry8() {
  ReefAngel.SetupCalibrateWaterLevel();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry9() {
  ReefAngel.SetupDateTime();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}

// Custom Main Screen
void DrawCustomMain() {
  char buf[16];
  byte x = 5;
  byte y = 2;
  byte t;

  // Main Header
  // ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 35, y,"Lee's Reef");
  // Had no room for this anymore :(
  
  // Date+Time
  ReefAngel.LCD.DrawDate(x+1, y);
  ReefAngel.LCD.Clear(COLOR_BLACK, 1, y+9, 128, y+9);
  
  // Param Header
  y+=12; 
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x+5,y,"Temp:");
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x+80, y, "PH:");
  // Temp and PH
  y+=2;
  ConvertNumToString(buf, ReefAngel.Params.Temp[T2_PROBE], 10);
  ReefAngel.LCD.DrawText(T2TempColor, DefaultBGColor, x+45, y, buf);
  y+=6; 
  ConvertNumToString(buf, ReefAngel.Params.Temp[T1_PROBE], 10);
  ReefAngel.LCD.DrawLargeText(T1TempColor, DefaultBGColor, x+5, y, buf, Num8x16);
  ConvertNumToString(buf, ReefAngel.Params.PH, 100);
  ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, x+80, y, buf, Num8x16);
  y+=5;
  ConvertNumToString(buf, ReefAngel.Params.Temp[T3_PROBE], 10);
  ReefAngel.LCD.DrawText(T3TempColor, DefaultBGColor, x+45, y, buf);
  pingSerial();
    
  /// Display Sunrise / Sunset (to be calculated later...)
  y+=12; t=x;
  sprintf(buf, "%02d:%02d", sl.GetRiseHour(), sl.GetRiseMinute());
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,t,y,"Rise:"); t+=31;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,buf); 
  sprintf(buf, "%02d:%02d", sl.GetSetHour(), sl.GetSetMinute()); t+=36;
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,t,y,"Set:"); t+=25;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,buf);
  pingSerial();

  // MoonPhase
  y+=10; 
  ReefAngel.LCD.DrawText(0,255,x,y,"Moon:");
  ReefAngel.LCD.Clear(DefaultBGColor,x+32,y,x+(128-x),y+8);
  ReefAngel.LCD.DrawText(COLOR_MAGENTA,255,x+32,y,MoonPhaseLabel());
  pingSerial();
  
  // MoonLight %
  y+=10;
  t = intlength(ReefAngel.PWM.GetDaylightValue()) + 1;  t *= 5;
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,x,y,"MoonLights:"); 
  ReefAngel.LCD.DrawSingleMonitor(ReefAngel.PWM.GetDaylightValue(), DPColor, x+68, y, 1);
  ReefAngel.LCD.DrawText(DPColor, DefaultBGColor, x+68+t, y, "%");
  pingSerial();

  // Display Water level
  y+=10; t=x;
  ConvertNumToString(buf, ReefAngel.WaterLevel.GetLevel(), 1);
  strcat(buf,"  ");
  ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,x,y,"AT0 Level:"); t+=60;
  ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,t,y,buf);

  // Vortech Mode
  y+=10; t=x;
  ReefAngel.LCD.DrawText(0,255,x,y,"RF:"); t+=20;
  ReefAngel.LCD.Clear(DefaultBGColor,t,y,x+(128-x),y+8);
  if (vtMode == 0) ReefAngel.LCD.DrawLargeText(COLOR_GREEN,255,t,y,"Constant");
  else if(vtMode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,t,y,"Lagoon");
  else if (vtMode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,t,y,"Reef Crest");
  else if (vtMode == 3) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,t,y,"Short Pulse");
  else if (vtMode == 4) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,t,y,"Long Pulse");
  else if (vtMode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,t,y,"Smart NTM");
  else if (vtMode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,t,y,"Tidal Swell");
  else if (vtMode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE,0,t,y,"Night");
  y+=10; t=x;
  ReefAngel.LCD.DrawText(0,255,x,y,"RF Speed:"); t+=60;
  ReefAngel.LCD.Clear(DefaultBGColor,t,y,x+(128-x),y+8);
  ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,t,y,InternalMemory.RFSpeed_read()); t+=15;
  ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,t,y,"/"); t+=10;
  ReefAngel.LCD.DrawText(COLOR_BLUE, DefaultBGColor,t,y,InternalMemory.RFDuration_read());
  pingSerial();
  
  // Display Water level
  y+=10; t=x;
  if (acclDay > 0) {
    ConvertNumToString(buf, acclDay, 1);
    strcat(buf,"  ");
    ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,x,y,"Acclimation Day:"); t+=100;
    ReefAngel.LCD.DrawText(DefaultFGColor,DefaultBGColor,t,y,buf);
  } else {
    ReefAngel.LCD.Clear(DefaultBGColor,x,y,x+(128-x),y+8);
  }
  
  // Relays
  y+=10; t=x+7;
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(t, y, TempRelay);
  pingSerial();
  y+=12;
  TempRelay = ReefAngel.Relay.RelayDataE[0];
  TempRelay &= ReefAngel.Relay.RelayMaskOffE[0];
  TempRelay |= ReefAngel.Relay.RelayMaskOnE[0];
  ReefAngel.LCD.DrawOutletBox(t, y, TempRelay);
  pingSerial();
}

void DrawCustomGraph() {
}
Post Reply