Sunrise / sunset code based on location

Do you have a question on how to do something.
Ask in here.
mliew
Posts: 12
Joined: Mon Jul 30, 2012 3:53 am

Sunrise / sunset code based on location

Post by mliew »

Hi guys,

I was just wondering if there is a possibility to code the sunrise/ sunset lights based on location and time?
Please see an example of a website.
http://suncalc.net/#/-37.8136,144.9631, ... 8.12/20:34
It will be so cool if it's possible. :lol:

thanks,
mliew
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

I am actually working on this. I'm looking to simplify one of these two code bases:

http://forum.reefangel.com/viewtopic.php?f=11&t=535
or
http://forum.reefangel.com/viewtopic.php?f=11&t=1450

The second one is the one I'll be most likely using as it seems to be based on latitude/longitude, but the first one is much is much simpler and simply sets the high/low during the seasons and adjusts each day accordingly..

Would love some help if you're feeling ambitious to strip the code down :)
mliew
Posts: 12
Joined: Mon Jul 30, 2012 3:53 am

Re: Sunrise / sunset code based on location

Post by mliew »

wow. great inevo. Please update and let me know your progress. I am a newbie with coding unfortunately and I am still in the process of building my setup at this stage. I will be looking to order the RA+ kit soon. But I will be studying the coding process as soon my setup is ready and when i order the RA kit.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

I posted my code in another thread, but the stripped down calculation code is in there. I don't know if it's working right and could use some help with someone more familiar with debug on this device....

So, bump if you can help on this.. I've got my "portable" setup working now, so hopefully should be able to do more testing soon..

http://forum.reefangel.com/viewtopic.php?p=12881#p12881
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Ok,the code is here and it seems to be working. Except for the fact that the time appears to be off by 1:28 minutes too early for both rise and set. when set to Salt Lake City as the code was written. And when I changed it to the Great Barrier Reef it was off by +56 minutes for sunrise and -19 minutes for sunset. I've hard coded some offsets into the calculation and added a user offset to match my schedule. So theoretically my lights will now on/off and have the same daytime/nightime as the GBR. :)

I'd like rufessor to take a look at the code that I've scavanged and see if I'm missing something critical... Anyway here's the code.

Code: Select all

//By Matthew Hockin 2012.  
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great. 
//If you copy from this (its all open source) please 
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).

#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 <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <ReefAngel.h>

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

//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>

//defines for SWFLTEK functions
#define _sec_rad 206264.806247096370813 // arc-seconds per radian
#define _tilt 0.409092804222329 // axial tilt of earth at epoch, in radians
#define _tropical_year 31556925 // tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _zenith 3.11250383272322 // 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)

//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
long latitude, longitude;
long elapsedTime;//used multiple places as elapsed since midnight
long newDay;
unsigned long rise;//time in seconds from the year 2000 (GMT) for sunrise
unsigned long set;//time in seconds from the year 2000 (GMT) for sunrise
byte dow=0;//day of week
//END HEADER FOR MHOCKIN Weather package //

// Additional Code added for Sunrise/Sunsets
int SunriseHour, SunriseMin, SunsetHour, SunsetMin;
int UserOffset=5*3600;

#define NUMBERS_8x16
#define CUSTOM_MENU
#define CUSTOM_MENU_ENTRIES   8

byte vtechmode;
byte vtmode = 0;
boolean bFeeding=false;
boolean isNight=false;
double MoonAge(int,int,int);
int JulianDate(int,int,int);
byte MoonState();
byte ThisPhase;
byte DayAge;

#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 = "ATO Clear";
prog_char menu5_label[] PROGMEM = "Overheat Clear";
prog_char menu6_label[] PROGMEM = "PH Calibration";
prog_char menu7_label[] PROGMEM = "Date / Time";
prog_char menu8_label[] PROGMEM = "Version";
// 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
};

//*********************************************************************************************************************************
//Define Relay Ports
#define Return             1
#define Skimmer            2
#define WhiteLEDs          3
#define BlueLEDs           4
#define Vortech            5
#define Heater             6
#define Refugium           7
#define UPS                8
//*********************************************************************************************************************************

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

void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}

void MenuEntry3()
{
   if(ReefAngel.RF.UseMemory) {
     vtmode++;
     if ( vtmode == 7 ) vtmode = 9;
     if ( vtmode > 9 ) vtmode = 0;
     InternalMemory.RFMode_write(vtmode);
     vtechmode = vtmode;
   }
   ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;   
}
void MenuEntry4()
{
ReefAngel.ATOClear();
ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry5()
{
ReefAngel.OverheatClear();
ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry6()
{
ReefAngel.SetupCalibratePH();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
ReefAngel.SetupDateTime();
ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry8()
{
ReefAngel.DisplayVersion();
}

void DrawCustomMain()
{
  char text[7];
  byte x = 5;
  byte y = 2;
  byte t;

  // Main Header
  ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 35, y,"Lee's Reef");
  ReefAngel.LCD.Clear(COLOR_BLACK, 1, y+9, 128, y+9);
 
  // Param Header
  y=y+20;
  ReefAngel.LCD.DrawText(T1TempColor,DefaultBGColor,x+5,y,"Temp:");
  ReefAngel.LCD.DrawText(PHColor,DefaultBGColor,x+60, y, "PH:");

  // Temp and PH
  y=y+10;
  // ReefAngel.LCD.Clear(DefaultBGColor,x,y,x+(128-x),y+16);
  ConvertNumToString(text, ReefAngel.Params.Temp[T1_PROBE], 10);
  ReefAngel.LCD.DrawLargeText(T1TempColor, DefaultBGColor, x+5, y, text, Num8x16);
  ConvertNumToString(text, ReefAngel.Params.PH, 100);
  ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, x+60, y, text, Num8x16);
  pingSerial();
  
  y=y+20;
  
  /// Display Sunrise / Sunset (to be calculated later...)
  char temp[]="  ";
  int xTemp=x;
  
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,xTemp,y,"Rise:"); xTemp+=31;
  itoa(SunriseHour,temp,10);
  if (SunriseHour<10) { 
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,"0"); xTemp+=6;
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,temp); xTemp+=6;
  } else {
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,temp); xTemp+=12;
  }
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,":"); xTemp+=6;
  if (SunriseMin<10) {
      ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,"0"); xTemp+=6;
  }
  itoa(SunriseMin,temp,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,temp); xTemp+=2;
  
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,xTemp,y,"Set:"); xTemp+=24;
  itoa(SunsetHour,temp,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,temp); xTemp+=10;
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,":"); xTemp+=6;
  if (SunsetMin<10) ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,"0"); xTemp+=6;
  itoa(SunsetMin,temp,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,xTemp,y,temp);

  // MoonPhase
  y=y+10;
  DayAge = MoonAge(day(), month(), year());
  MoonState(DayAge);
  char* ThisPhaseLabel[]={ "New","Waxing Crescent","First Quarter","Waxing Gibbous","Full","Waning Gibbous","Last Quarter","Waning Crescent" };
  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,ThisPhaseLabel[ThisPhase]);
  pingSerial();
  
  // MoonLight %
  y=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();

  // Vortech Mode
  y=y+10;
  ReefAngel.LCD.DrawText(0,255,x,y,"RF:");
  ReefAngel.LCD.Clear(DefaultBGColor,x+20,y,x+(128-x),y+8);
  if (vtechmode == 0) ReefAngel.LCD.DrawLargeText(COLOR_LIMEGREEN,255,x+20,y,"Constant");
  else if(vtechmode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,x+20,y,"Lagoon");
  else if (vtechmode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,x+20,y,"Reef Crest");
  else if (vtechmode == 3) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,x+20,y,"Short Pulse");
  else if (vtechmode == 4) ReefAngel.LCD.DrawLargeText(COLOR_CORNFLOWERBLUE,255,x+20,y,"Long Pulse");
  else if (vtechmode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,x+20,y,"Smart NTM");
  else if (vtechmode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,x+20,y,"Tidal Swell");
  else if (vtechmode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE,0,x+20,y,"Night");
  pingSerial();
  
  // Relays
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(12, 103, TempRelay);
  pingSerial();
  
  // Date+Time
  ReefAngel.LCD.DrawDate(6, 122);
  pingSerial();
}

void DrawCustomGraph()
{
//  ReefAngel.LCD. DrawGraph(5, 15);
}


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = Port4Bit | Port7Bit ;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port6Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;
    // Set the Overheat temperature setting
    InternalMemory.OverheatTemp_write( 820 );

    // Ports that are always on
    ReefAngel.Relay.On( Return );
    ReefAngel.Relay.On( Vortech );
    ReefAngel.Relay.On( UPS );
    
    ////// Place additional initialization code below here
    InternalMemory.RFMode_write(2);
    InternalMemory.RFSpeed_write(50);
    InternalMemory.RFDuration_write(10);
    
    ReefAngel.RF.UseMemory=true;
    vtechmode = InternalMemory.RFMode_read();
    ////// Place additional initialization code above here
}

void loop()
{
    // Set Sunrise and Sunset from MHOCKIN Weather package functions. 
    elapsedTime=(now()-newDay);  //Elapsed time is seconds from midnight of today- local processor time.
    if (dow!=day()){ //used to see that were in a new day and need to recalculate sunrise and sunset
      CalSun();
      dow=day();
    } 

    ReefAngel.Relay.DelayedOn( Skimmer,5 );
    ReefAngel.StandardLights( WhiteLEDs,12,0,20,0 );
    ReefAngel.StandardLights( BlueLEDs,11,0,23,0 );
    ReefAngel.StandardHeater( Heater,788,792 );
    
    ////// Place your custom code below here
    
    // if (( hour() <= 11 ) || ( hour() >= 23 )) isNight=true; else isNight=false;
    int currTime = hour()*100+minute();
    int riseTime=SunriseHour*100+SunriseMin;
    int setTime=SunsetHour*100+SunsetMin;
    
    // if (( hour() <=11 ) || ( hour() >= 23 )) isNight=true; else isNight=false;
    if (( currTime <= riseTime ) || ( currTime >= setTime )) isNight=true; else isNight=false;
    
    if (ReefAngel.DisplayedMenu==FEEDING_MODE) bFeeding=true;
    if (ReefAngel.DisplayedMenu==DEFAULT_MENU && bFeeding )
    {
      bFeeding=false; 
      ReefAngel.RF.UseMemory=false;
      ReefAngel.RF.SetMode(Smart_NTM,100,5);
      ReefAngel.Timer[4].SetInterval(600); // Timer for 10min
      ReefAngel.Timer[4].Start();
      vtechmode = 5;
    }
    
    if (ReefAngel.DisplayedMenu==DEFAULT_MENU && ReefAngel.Timer[4].IsTriggered()) 
    {
      if (isNight) 
      {
        ReefAngel.RF.UseMemory=false;
        ReefAngel.RF.SetMode(Night,15,0);
        vtechmode = 9;
      } else {
        ReefAngel.RF.UseMemory=true;
        vtechmode = InternalMemory.RFMode_read();
      }
    }
    
    if (isNight) 
    {
      ReefAngel.PWM.SetDaylight( MoonPhase() );
      ReefAngel.PWM.SetActinic( MoonPhase() );
      ReefAngel.Relay.On( Refugium );
    } else { 
      ReefAngel.PWM.SetDaylight( 0 );
      ReefAngel.PWM.SetActinic( 0 );
      ReefAngel.Relay.Off( Refugium );
    }   
    
    ////// Place your custom code above here

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

int JulianDate(int d, int m, int y)
{ 
  int mm, yy;
  int k1, k2, k3;
  int j;

  yy = y - (int)((12 - m) / 10);
  mm = m + 9;
  if (mm >= 12)
  {
    mm = mm - 12;
  }
  k1 = (int)(365.25 * (yy + 4712));
  k2 = (int)(30.6001 * mm + 0.5);
  k3 = (int)((int)((yy / 100) + 49) * 0.75) - 38;
  // 'j' for dates in Julian calendar:
  j = k1 + k2 + d + 59;
  if (j > 2299160)
  {
    // For Gregorian calendar:
    j = j - k3; // 'j' is the Julian date at 12h UT (Universal Time)
  }
  return j;
}

double MoonAge(int d, int m, int y)
{ 
  int j = JulianDate(d, m, y);
  //Calculate the approximate phase of the moon
  int ip = (j + 4.867) / 29.53059;
  ip = ip - abs(ip); 
  //After several trials I've seen to add the following lines, 
  //which gave the result was not bad 
  if(ip < 0.5)
    int ag = ip * 29.53059 + 29.53059 / 2;
  else
    int ag = ip * 29.53059 - 29.53059 / 2;
  // Moon's age in days
  byte ag = abs(ag) + 1;
  return ag;
}

byte MoonState(byte D)
{
  switch(D){
  case 1: 
    0, 29;
    ThisPhase = 0;
  case 2: 
    1, 2, 3, 4, 5, 6;
    ThisPhase = 1;
  case 3: 
    7;
    ThisPhase = 2;
  case 4: 
    8, 9, 10, 11, 12, 13;
    ThisPhase = 3;
  case 5: 
    14;
    ThisPhase = 4;
  case 6: 
    15, 16, 17, 18, 19, 20, 21;
    ThisPhase = 5;
  case 7: 
    22;
    ThisPhase = 6;
  case 8: 
    23, 24, 25, 26, 27, 28;
    ThisPhase = 7;
  default: 
    return 0;
  }
}

//Start of sunrise, sunset and cloud calculations- runs on reset and once a day thereafter.
void CalSun(){
   //Serial.println("CalSun Run Now");

       // latitude=dmsToSeconds(40,44,00);//United States of America- Salt Lake City, local time is -7 hours GMT 
       // longitude=dmsToSeconds(-111,47,00);
       latitude=dmsToSeconds(18,34,4);// Great Barrier Reef, Australia
       longitude=dmsToSeconds(148,33,19); // Great Barrier Reef, Australia
       //**********************ok now were done changing things IF YOU CHANGED the Top part of the GLOBAL variable decleration AND this... your FULLY configured and ready to load******************************************** 
   
    if (dow==0){//if the controller has resarted we need to find midnight
      long hours, minutes;//store current elapsed local hours as total seconds from midnight
      time_t t=now();//store current clock time to parse
      hours=hour(t);
      hours=(hours*3600);//current hour number 0-23 as seconds
      minutes=minute(t);
      minutes=(minutes*60);//minutes of current hour as seconds
      newDay=now();
      newDay-=(hours+minutes);//Convert current local unix epoch time to local unix epoch time of midnight
    }
    else if (dow!=0){//if we did not restart but the day is new then it is midnight and were good to go..
      newDay=now();
    }
      
    //#define SECS_YR_2000 (946684800) the time at the start of y2k (need to subtract from unix epoch time to bring to Y2K origin
    newDay-=946684800;//convert GMT unix Epoch to seconds elasped since 2000 for GMT midnight of today
    
    rise=newDay;//set value to send to SunRise as midnight GMT in seconds from Y2K
    set=newDay;//
    //Calculate rise time and set time using Epherma Library functions (see end of code) 
    SunRise(&rise);//call to Epherma function
    SunSet(&set);//Call to Epherma functionunsigned long newDay;
  
   /*Serial.print("rise and set=  ");
   Serial.println(rise);
   Serial.println(set);
   Serial.print("newDay as seconds since 2000 to todays midnight=  ");
   Serial.println(newDay);*/
   
   SunriseHour=hour(rise+(56*60)+UserOffset);
   SunriseMin=minute(rise+(56*60)+UserOffset);
   SunsetHour=hour(set-(19*60)+UserOffset);
   SunsetMin=minute(set-(19*60)+UserOffset);
   
   rise=(rise-newDay);// set to elapsed seconds of day
   set=(set-newDay);
   /*Serial.print("rise and set as elapsed seconds of day=  ");
   Serial.println(rise);
   Serial.println(set);*/
   newDay+=946684800;//Convert newDay back to unix epoch GMT midnight today (used in loop to determine how far we are into the day) 
   /*Serial.print("newDay as seconds since since 1970 to todays midnight=  ");
   Serial.println(newDay);
   Serial.print("elapsed is");
   long elapsed=now()-newDay;
   Serial.println(elapsed);*/
   
 }//END SunCalc FUNCTION

//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.  
//this code is being used freely with attribution to Micahel Rice in accord with his request
//  A big thank you for these library functions.  Its great! 

// decimal degrees
long ddToSeconds(float dd){
   return dd * 3600.0;
}

//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;

   ret = labs((long)d);
   ret = ret * 3600L + 60L * m + s;
   ret = (d<0L) ? -ret : ret;
   return ret;
}
/* ------------------------------------------------------------------------------------------------
   'Equation of Time'
   We use the 'short form equation, which has a theoretical accuracy of about 40 seconds.
   The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;

   dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
   dt %= _tropical_year;
   t = dt;
   t /= _tropical_year;
   t *= 6.283185307179586;
   t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
   return t;
}


/* -----------------------------------------------------------------------------------------------
   'Solar Declination'
   Returns declination in radians
   Accurate to within 50 arc-seconds
*/

double SolarDeclination(unsigned long dt){
double y;

   dt %= _tropical_year;
   y = dt;
   y /= _tropical_year; // fractional year
   y *= 6.283185307179586;
   y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
   return y;
}

/* ------------------------------------------------------------------------------------------------
   Return the period between sunrise and sunset, in seconds.
   At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;

   d = -SolarDeclination(dt); // will be positive in Northern winter
   l = latitude / _sec_rad; // latitude in radians

   e += 60.0 * l * tan(l + d); // latitudinal error
   d = tan(l) * tan(d); //

   if(d>1.0) return 86400UL;
   if(d < -1.0) return 0UL;

   d = acos(d);
   d /= _zenith;

   n = 86400UL * d;
   n += e;
   return n;
}


/* ------------------------------------------------------------------------------------------------
   Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
   Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
   returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
   all day.

*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;

   daylen = daylightseconds(*dt);
   if(daylen == 86400UL) return 1;   // there is no 'night' today (midnight sun)
   if(daylen == 0UL) return -1; // there is no 'day' today

   *dt /= 86400UL;
   *dt *= 86400UL;
   *dt += 43200UL; // set the time stamp to 12:00:00 GMT

   *dt -= daylen / 2; //        sunrise at the prime meridian
   if(set) *dt += daylen; //     sunset at the prime meridian

   *dt -= equation_of_time(*dt);

   //*dt -= longitude / 15.0; // rotate to our own meridian

   return 0;
}

// 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
    return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
    return SunRiseSet(when, 1);
}
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: Sunrise / sunset code based on location

Post by rufessor »

Hi-

A couple comments... I have only briefly looked at your code mostly looking for pieces I recognize and its looking like you have the basics from my code in and they look pretty much ok. In terms of the offsets you added I need a few minutes to look so I have no comment just now.

But- with respect to rise and set times, being off by less than 2 minutes is basically incredibly good accuracy for this algorithim. So that pretty much confirms you have the stuff I implemented (some of which I took from Michael Rice) working. As for why the great barrier reef is giving you about 1 hour error... I will be looking at your code to be sure, but it could either be lat lon error or possibly just a 1 hour error in your time calculation (did not see it yet) which is affecting rise but not set and the error in the calculations is accounting for the rest. Will look more and post back. You could just run some serial debugging and see if you can test some other locations on the globe....
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Sunrise / sunset code based on location

Post by lnevo »

Just to clarify the offsets were +1:28 in SLC and +56/-19 at GBR. This was hours/minutes not minutes/seconds..

Thanks for taking a look!!!

I haven't learned how to do serial debugging yet :)
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Ok, I looked again, and my coordinates were off. I fixed that and my time looks much better however still 20 minutes or so off. It actually fits my viewing schedule a bit better so I'm going to leave it. I changed the time to a few dates within the calendar and it does seem to be following the light cycle but the variation of offset does vary. I guess it's as close as we're going to get.

For those that want to do similar here is my final Sunrise/Sunset code changes. Thanks to rufessor for the pre-work that was involved. I could not have done it without his efforts! Thanks!

Code: Select all

//By Matthew Hockin 2012.  
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great. 
//If you copy from this (its all open source) please 
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).

#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 <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <ReefAngel.h>

////// Place global variable code below here
#define NUMBERS_8x16
#define CUSTOM_MENU
#define CUSTOM_MENU_ENTRIES   8

//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>
//defines for SWFLTEK functions
#define _sec_rad 206264.806247096370813 // arc-seconds per radian
#define _tilt 0.409092804222329 // axial tilt of earth at epoch, in radians
#define _tropical_year 31556925 // tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _zenith 3.11250383272322 // 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)

//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
long latitude, longitude;
long newDay;
unsigned long rise;//time in seconds from the year 2000 (GMT) for sunrise
unsigned long set;//time in seconds from the year 2000 (GMT) for sunrise
byte dow=0;//day of week
//*******************END HEADER FOR MHOCKIN Weather package*******************************************************

// Additional variables added for Sunrise/Sunset calculation
int SunriseHour, SunriseMin, SunsetHour, SunsetMin;
int UserOffset=5*3600; // 5 hours

byte vtechmode=Random2;
byte vtechDefault=Random2;
int vtechSpeed=50;
int vtechDuration=10;
boolean bFeeding=false;
boolean isNight=false;

double MoonAge(int,int,int);
int JulianDate(int,int,int);
byte MoonState();
byte ThisPhase;
byte DayAge;

#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 = "ATO Clear";
prog_char menu5_label[] PROGMEM = "Overheat Clear";
prog_char menu6_label[] PROGMEM = "PH Calibration";
prog_char menu7_label[] PROGMEM = "Date / Time";
prog_char menu8_label[] PROGMEM = "Version";
// 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
};

//*********************************************************************************************************************************
//Define Relay Ports
#define Return             1
#define Skimmer            2
#define WhiteLEDs          3
#define BlueLEDs           4
#define Vortech            5
#define Heater             6
#define Refugium           7
#define UPS                8
//*********************************************************************************************************************************

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

void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}

void MenuEntry3()
{
  byte vtmode=vtechmode;
  
  vtmode++;
  if ( vtmode == 7 ) vtmode = 9;
  if ( vtmode > 9 ) vtmode = 0;
  vtechmode=vtmode;
 
  ReefAngel.RF.UseMemory=false;
  ReefAngel.RF.SetMode(Feeding_Stop,0,0); //Temp fix for coming out of Night mode
  ReefAngel.RF.SetMode(vtmode,ReefAngel.RF.Speed,ReefAngel.RF.Duration);
  ReefAngel.Timer[4].SetInterval(10); // Timer for 30min
  ReefAngel.Timer[4].Start();
  
  ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;   
}
void MenuEntry4()
{
  ReefAngel.ATOClear();
  ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry5()
{
  ReefAngel.OverheatClear();
  ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry6()
{
  ReefAngel.SetupCalibratePH();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
  ReefAngel.SetupDateTime();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
  CalSun();
}
void MenuEntry8()
{
  ReefAngel.DisplayVersion();
}

void DrawCustomMain()
{
  char text[7];
  byte x = 5;
  byte y = 2;
  byte t;

  // Main Header
  ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 35, y,"Lee's Reef");
  ReefAngel.LCD.Clear(COLOR_BLACK, 1, y+9, 128, y+9);

  // Param Header
  y=y+20; 
  ReefAngel.LCD.DrawText(T1TempColor,DefaultBGColor,x+5,y,"Temp:");
  ReefAngel.LCD.DrawText(PHColor,DefaultBGColor,x+60, y, "PH:");

  // Temp and PH
  y=y+10; 
  ConvertNumToString(text, ReefAngel.Params.Temp[T1_PROBE], 10);
  ReefAngel.LCD.DrawLargeText(T1TempColor, DefaultBGColor, x+5, y, text, Num8x16);
  ConvertNumToString(text, ReefAngel.Params.PH, 100);
  ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, x+60, y, text, Num8x16);
  pingSerial();
  
  /// Display Sunrise / Sunset (to be calculated later...)
  y=y+20; t=x;
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,t,y,"Rise:"); t+=31;
  itoa(SunriseHour,text,10);
  if (SunriseHour<10) { 
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6;
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=6;
  } else {
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=12;
  }
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,":"); t+=6;
  if (SunriseMin<10) {
      ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6;
  }
  itoa(SunriseMin,text,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=18;
  
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,t,y,"Set:"); t+=24;
  itoa(SunsetHour,text,10);
  if (SunsetHour<10) { 
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6;
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=6;
  } else {
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=12;
  }
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,":"); t+=6;
  if (SunsetMin<10) { ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6; }
  itoa(SunsetMin,text,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text);

  // MoonPhase
  y=y+10; 
  DayAge = MoonAge(day(), month(), year());
  MoonState(DayAge);
  char* ThisPhaseLabel[]={ "New","Waxing Crescent","First Quarter","Waxing Gibbous","Full","Waning Gibbous","Last Quarter","Waning Crescent" };
  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,ThisPhaseLabel[ThisPhase]);
  pingSerial();
  
  // MoonLight %
  y=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();

  // Vortech Mode
  y=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 (vtechmode == 0) ReefAngel.LCD.DrawLargeText(COLOR_LIMEGREEN,255,t,y,"Constant");
  else if(vtechmode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,t,y,"Lagoon");
  else if (vtechmode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,t,y,"Reef Crest");
  else if (vtechmode == 3) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,t,y,"Short Pulse");
  else if (vtechmode == 4) ReefAngel.LCD.DrawLargeText(COLOR_CORNFLOWERBLUE,255,t,y,"Long Pulse");
  else if (vtechmode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,t,y,"Smart NTM");
  else if (vtechmode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,t,y,"Tidal Swell");
  else if (vtechmode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE,0,t,y,"Night");
  pingSerial();
  
  // Relays
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(12, 103, TempRelay);
  pingSerial();
  
  // Date+Time
  ReefAngel.LCD.DrawDate(6, 122);
  pingSerial();
}

void DrawCustomGraph()
{
}


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = Port4Bit | Port7Bit ;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port6Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;
    // Set the Overheat temperature setting
    InternalMemory.OverheatTemp_write( 820 );

    // Ports that are always on
    ReefAngel.Relay.On( Return );
    ReefAngel.Relay.On( Vortech );
    ReefAngel.Relay.On( UPS );
    
    ////// Place additional initialization code below here
    
    // Reset Vortech to preferred defaults
    InternalMemory.RFMode_write(vtechDefault);
    InternalMemory.RFSpeed_write(vtechSpeed);
    InternalMemory.RFDuration_write(vtechDuration);
    ReefAngel.RF.UseMemory=true;
   
    ////// Place additional initialization code above here
}

void loop()
{
    ReefAngel.Relay.DelayedOn( Skimmer,5 );
    ReefAngel.StandardHeater( Heater,788,792 );
    
    ////// Place your custom code below here

    // Set Sunrise and Sunset from MHOCKIN Weather package functions. 
    if (dow!=day()) // used to see that were in a new day and need to recalculate sunrise and sunset
    {
      CalSun();
      dow=day();
      // And let's reset the light schedule
    } 

    ReefAngel.StandardLights( WhiteLEDs,SunriseHour+1,SunriseMin,SunsetHour-3,SunsetMin );
    ReefAngel.StandardLights( BlueLEDs,SunriseHour,SunriseMin,SunsetHour,SunsetMin );
    
    int currTime = hour()*100+minute();
    int riseTime=SunriseHour*100+SunriseMin;
    int setTime=SunsetHour*100+SunsetMin;
    
    if (( currTime <= riseTime ) || ( currTime >= setTime )) isNight=true; else isNight=false;
    
    if (isNight) 
    {
      ReefAngel.PWM.SetDaylight( MoonPhase() );
      ReefAngel.PWM.SetActinic( MoonPhase() );
      ReefAngel.Relay.On( Refugium );
      
      if (ReefAngel.RF.UseMemory && ReefAngel.RF.Mode!=Night) { 
        ReefAngel.RF.SetMode(Night,15,0);
      }
    } else { 
      ReefAngel.PWM.SetDaylight( 0 );
      ReefAngel.PWM.SetActinic( 0 );
      ReefAngel.Relay.Off( Refugium );

      // It's Daytime!
      if (ReefAngel.RF.UseMemory && ReefAngel.RF.Mode==Night) {
          ReefAngel.RF.SetMode(Feeding_Stop,0,0); //Temp fix for coming out of Night mode
          ReefAngel.RF.SetMode(vtechDefault,15,0);
      }
    }   

    if (ReefAngel.DisplayedMenu==FEEDING_MODE) bFeeding=true;
    if (ReefAngel.DisplayedMenu==DEFAULT_MENU && bFeeding )
    {
      bFeeding=false; 
      ReefAngel.RF.UseMemory=false;
      ReefAngel.RF.SetMode(Smart_NTM,100,5);
      ReefAngel.Timer[4].SetInterval(1800); // Timer for 30min
      ReefAngel.Timer[4].Start();
      vtechmode = Smart_NTM;
    }
    if (ReefAngel.DisplayedMenu==DEFAULT_MENU && ReefAngel.Timer[4].IsTriggered()) 
    {
      ReefAngel.RF.UseMemory=true;
    }

    if (ReefAngel.RF.UseMemory) { 
      vtechmode=ReefAngel.RF.Mode;
    }
    
    ////// Place your custom code above here

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

int JulianDate(int d, int m, int y)
{ 
  int mm, yy;
  int k1, k2, k3;
  int j;

  yy = y - (int)((12 - m) / 10);
  mm = m + 9;
  if (mm >= 12)
  {
    mm = mm - 12;
  }
  k1 = (int)(365.25 * (yy + 4712));
  k2 = (int)(30.6001 * mm + 0.5);
  k3 = (int)((int)((yy / 100) + 49) * 0.75) - 38;
  // 'j' for dates in Julian calendar:
  j = k1 + k2 + d + 59;
  if (j > 2299160)
  {
    // For Gregorian calendar:
    j = j - k3; // 'j' is the Julian date at 12h UT (Universal Time)
  }
  return j;
}

double MoonAge(int d, int m, int y)
{ 
  int j = JulianDate(d, m, y);
  //Calculate the approximate phase of the moon
  int ip = (j + 4.867) / 29.53059;
  ip = ip - abs(ip); 
  //After several trials I've seen to add the following lines, 
  //which gave the result was not bad 
  if(ip < 0.5)
    int ag = ip * 29.53059 + 29.53059 / 2;
  else
    int ag = ip * 29.53059 - 29.53059 / 2;
  // Moon's age in days
  byte ag = abs(ag) + 1;
  return ag;
}

byte MoonState(byte D)
{
  switch(D){
  case 1: 
    0, 29;
    ThisPhase = 0;
  case 2: 
    1, 2, 3, 4, 5, 6;
    ThisPhase = 1;
  case 3: 
    7;
    ThisPhase = 2;
  case 4: 
    8, 9, 10, 11, 12, 13;
    ThisPhase = 3;
  case 5: 
    14;
    ThisPhase = 4;
  case 6: 
    15, 16, 17, 18, 19, 20, 21;
    ThisPhase = 5;
  case 7: 
    22;
    ThisPhase = 6;
  case 8: 
    23, 24, 25, 26, 27, 28;
    ThisPhase = 7;
  default: 
    return 0;
  }
}

//Start of sunrise, sunset and cloud calculations- runs on reset and once a day thereafter.
void CalSun(){
   //Serial.println("CalSun Run Now");

       // latitude=dmsToSeconds(40,44,00);//United States of America- Salt Lake City, local time is -7 hours GMT 
       // longitude=dmsToSeconds(-111,47,00);
       latitude=dmsToSeconds(-18,17,9);// Great Barrier Reef, Australia
       longitude=dmsToSeconds(147,41,59); // Great Barrier Reef, Australia
       //**********************ok now were done changing things IF YOU CHANGED the Top part of the GLOBAL variable decleration AND this... your FULLY configured and ready to load******************************************** 
   
    if (dow==0){//if the controller has resarted we need to find midnight
      long hours, minutes;//store current elapsed local hours as total seconds from midnight
      time_t t=now();//store current clock time to parse
      hours=hour(t);
      hours=(hours*3600);//current hour number 0-23 as seconds
      minutes=minute(t);
      minutes=(minutes*60);//minutes of current hour as seconds
      newDay=now();
      newDay-=(hours+minutes);//Convert current local unix epoch time to local unix epoch time of midnight
    }
    else if (dow!=0){//if we did not restart but the day is new then it is midnight and were good to go..
      newDay=now();
    }
      
    //#define SECS_YR_2000 (946684800) the time at the start of y2k (need to subtract from unix epoch time to bring to Y2K origin
    newDay-=946684800;//convert GMT unix Epoch to seconds elasped since 2000 for GMT midnight of today
    
    rise=newDay;//set value to send to SunRise as midnight GMT in seconds from Y2K
    set=newDay;//
    //Calculate rise time and set time using Epherma Library functions (see end of code) 
    SunRise(&rise);//call to Epherma function
    SunSet(&set);//Call to Epherma functionunsigned long newDay;
  
   // Convert rise/set to normal hours/minutes before we make it an offset from midnight. 
   // Currently correcting for a 56 minute correction to Sunrise and -19 minute correction to Sunset (8/14/2012@GBR)
   SunriseHour=hour(rise+UserOffset);
   SunriseMin=minute(rise+UserOffset);
   SunsetHour=hour(set+UserOffset);
   SunsetMin=minute(set+UserOffset);

   /*Serial.print("rise and set=  ");
   Serial.println(rise);
   Serial.println(set);
   Serial.print("newDay as seconds since 2000 to todays midnight=  ");
   Serial.println(newDay);*/
   rise=(rise-newDay);// set to elapsed seconds of day
   set=(set-newDay);
   /*Serial.print("rise and set as elapsed seconds of day=  ");
   Serial.println(rise);
   Serial.println(set);*/
   newDay+=946684800;//Convert newDay back to unix epoch GMT midnight today (used in loop to determine how far we are into the day) 
   /*Serial.print("newDay as seconds since since 1970 to todays midnight=  ");
   Serial.println(newDay);
   Serial.print("elapsed is");
   long elapsed=now()-newDay;
   Serial.println(elapsed);*/
   
}//END SunCalc FUNCTION

//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.  
//this code is being used freely with attribution to Micahel Rice in accord with his request
//  A big thank you for these library functions.  Its great! 

// decimal degrees
long ddToSeconds(float dd){
   return dd * 3600.0;
}

//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;

   ret = labs((long)d);
   ret = ret * 3600L + 60L * m + s;
   ret = (d<0L) ? -ret : ret;
   return ret;
}
/* ------------------------------------------------------------------------------------------------
   'Equation of Time'
   We use the 'short form equation, which has a theoretical accuracy of about 40 seconds.
   The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;

   dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
   dt %= _tropical_year;
   t = dt;
   t /= _tropical_year;
   t *= 6.283185307179586;
   t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
   return t;
}

/* -----------------------------------------------------------------------------------------------
   'Solar Declination'
   Returns declination in radians
   Accurate to within 50 arc-seconds
*/
double SolarDeclination(unsigned long dt){
double y;

   dt %= _tropical_year;
   y = dt;
   y /= _tropical_year; // fractional year
   y *= 6.283185307179586;
   y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
   return y;
}

/* ------------------------------------------------------------------------------------------------
   Return the period between sunrise and sunset, in seconds.
   At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;

   d = -SolarDeclination(dt); // will be positive in Northern winter
   l = latitude / _sec_rad; // latitude in radians

   e += 60.0 * l * tan(l + d); // latitudinal error
   d = tan(l) * tan(d); //

   if(d>1.0) return 86400UL;
   if(d < -1.0) return 0UL;

   d = acos(d);
   d /= _zenith;

   n = 86400UL * d;
   n += e;
   return n;
}


/* ------------------------------------------------------------------------------------------------
   Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
   Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
   returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
   all day.

*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;

   daylen = daylightseconds(*dt);
   if(daylen == 86400UL) return 1;   // there is no 'night' today (midnight sun)
   if(daylen == 0UL) return -1; // there is no 'day' today

   *dt /= 86400UL;
   *dt *= 86400UL;
   *dt += 43200UL; // set the time stamp to 12:00:00 GMT

   *dt -= daylen / 2; //        sunrise at the prime meridian
   if(set) *dt += daylen; //     sunset at the prime meridian

   *dt -= equation_of_time(*dt);

   //*dt -= longitude / 15.0; // rotate to our own meridian

   return 0;
}

// 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
    return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
    return SunRiseSet(when, 1);
}
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Sunrise / sunset code based on location

Post by rimai »

So now, how can we get that all into a single function?? :)
Roberto.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Sunrise / sunset code based on location

Post by lnevo »

I don't think it can be one function, but the only external function you need in your code is CalSun().

You could rearrange some code so that the variables that I'm using is just a time_t that you pass to CalSun() and drop all the required functions/defines into an #include then you'd just need to config your latitude and longitude...

You still need to make sure to call CalSun() once a day and handle your own offset, unless you add that to the function as well.

Shouldn't be too tough. Let me see what i can do later..

Edit: scratch some of that..we meed two times... Let me think about it..
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: Sunrise / sunset code based on location

Post by binder »

you could have a function that takes multiple params to set the initial values. then have a function that returns/sets the sunrise / sunset values for use later in the loop or wherever else it will be used. honestly, the best idea for this is to create a class that is part of the reefangel class (like pwm and others). then have all the functions encapsulated within and it makes it really easy to pull the values out and work with them wherever needed.
i can help with that part if someone can isolate the code in use for calculations (or confirm what i am thinking) then test things that i write.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

I can strip out my code pretty easily.. and there's definitely some better ways to manage the variables that I'm using now. Spent my entire commute pretty much thinking this through... let me strip out just the code needed and post it here....

Ok, here's everything stripped out. We can probably remove my variables (SunriseHour, SunriseMin, SunsetHour, SunsetMin) and just use rise and set. I don't think the newDay variable needs to be global and have doubts about dow. I think we can just run CalSun() all day in the loop() function and ignore dow. This way we'd only have rise, set, latitude, and longitude. There's also an unused function ddToSeconds this way we can just define the coordinates in the global declaration...userOffset is definitely an optional variable and we can let users do that themselves...

Code: Select all

//By Matthew Hockin 2012.  
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great. 
//If you copy from this (its all open source) please 
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).

#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 <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <ReefAngel.h>

////// Place global variable code below here
#define NUMBERS_8x16
#define CUSTOM_MENU
#define CUSTOM_MENU_ENTRIES   8

//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>
//defines for SWFLTEK functions
#define _sec_rad 206264.806247096370813 // arc-seconds per radian
#define _tilt 0.409092804222329 // axial tilt of earth at epoch, in radians
#define _tropical_year 31556925 // tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _zenith 3.11250383272322 // 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)

//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
long latitude, longitude;
long newDay;
unsigned long rise;//time in seconds from the year 2000 (GMT) for sunrise
unsigned long set;//time in seconds from the year 2000 (GMT) for sunrise
byte dow=0;//day of week
//*******************END HEADER FOR MHOCKIN Weather package*******************************************************

// Additional variables added for Sunrise/Sunset calculation
int SunriseHour, SunriseMin, SunsetHour, SunsetMin;
int UserOffset=5*3600; // 5 hours

byte vtechmode=Random2;
byte vtechDefault=Random2;
int vtechSpeed=50;
int vtechDuration=10;
boolean bFeeding=false;
boolean isNight=false;

double MoonAge(int,int,int);
int JulianDate(int,int,int);
byte MoonState();
byte ThisPhase;
byte DayAge;

#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 = "ATO Clear";
prog_char menu5_label[] PROGMEM = "Overheat Clear";
prog_char menu6_label[] PROGMEM = "PH Calibration";
prog_char menu7_label[] PROGMEM = "Date / Time";
prog_char menu8_label[] PROGMEM = "Version";
// 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
};

//*********************************************************************************************************************************
//Define Relay Ports
#define Return             1
#define Skimmer            2
#define WhiteLEDs          3
#define BlueLEDs           4
#define Vortech            5
#define Heater             6
#define Refugium           7
#define UPS                8
//*********************************************************************************************************************************

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

void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}

void MenuEntry3()
{
  byte vtmode=vtechmode;
  
  vtmode++;
  if ( vtmode == 7 ) vtmode = 9;
  if ( vtmode > 9 ) vtmode = 0;
  vtechmode=vtmode;

  ReefAngel.RF.UseMemory=false;
  ReefAngel.RF.SetMode(Feeding_Stop,0,0); //Temp fix for coming out of Night mode
  ReefAngel.RF.SetMode(vtmode,ReefAngel.RF.Speed,ReefAngel.RF.Duration);
  ReefAngel.Timer[4].SetInterval(10); // Timer for 30min
  ReefAngel.Timer[4].Start();
  
  ReefAngel.DisplayedMenu = RETURN_MAIN_MODE;   
}
void MenuEntry4()
{
  ReefAngel.ATOClear();
  ReefAngel.DisplayMenuEntry("Clear ATO Timeout");
}
void MenuEntry5()
{
  ReefAngel.OverheatClear();
  ReefAngel.DisplayMenuEntry("Clear Overheat");
}
void MenuEntry6()
{
  ReefAngel.SetupCalibratePH();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
}
void MenuEntry7()
{
  ReefAngel.SetupDateTime();
  ReefAngel.DisplayedMenu = ALT_SCREEN_MODE;
  CalSun();
}
void MenuEntry8()
{
  ReefAngel.DisplayVersion();
}

void DrawCustomMain()
{
  char text[7];
  byte x = 5;
  byte y = 2;
  byte t;

  // Main Header
  ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 35, y,"Lee's Reef");
  ReefAngel.LCD.Clear(COLOR_BLACK, 1, y+9, 128, y+9);

  // Param Header
  y=y+20; 
  ReefAngel.LCD.DrawText(T1TempColor,DefaultBGColor,x+5,y,"Temp:");
  ReefAngel.LCD.DrawText(PHColor,DefaultBGColor,x+60, y, "PH:");

  // Temp and PH
  y=y+10; 
  ConvertNumToString(text, ReefAngel.Params.Temp[T1_PROBE], 10);
  ReefAngel.LCD.DrawLargeText(T1TempColor, DefaultBGColor, x+5, y, text, Num8x16);
  ConvertNumToString(text, ReefAngel.Params.PH, 100);
  ReefAngel.LCD.DrawLargeText(PHColor, DefaultBGColor, x+60, y, text, Num8x16);
  pingSerial();
  
  /// Display Sunrise / Sunset (to be calculated later...)
  y=y+20; t=x;
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,t,y,"Rise:"); t+=31;
  itoa(SunriseHour,text,10);
  if (SunriseHour<10) { 
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6;
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=6;
  } else {
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=12;
  }
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,":"); t+=6;
  if (SunriseMin<10) {
      ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6;
  }
  itoa(SunriseMin,text,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=18;
  
  ReefAngel.LCD.DrawText(COLOR_BLACK,DefaultBGColor,t,y,"Set:"); t+=24;
  itoa(SunsetHour,text,10);
  if (SunsetHour<10) { 
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6;
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=6;
  } else {
    ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text); t+=12;
  }
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,":"); t+=6;
  if (SunsetMin<10) { ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,"0"); t+=6; }
  itoa(SunsetMin,text,10);
  ReefAngel.LCD.DrawText(COLOR_RED,DefaultBGColor,t,y,text);

  // MoonPhase
  y=y+10; 
  DayAge = MoonAge(day(), month(), year());
  MoonState(DayAge);
  char* ThisPhaseLabel[]={ "New","Waxing Crescent","First Quarter","Waxing Gibbous","Full","Waning Gibbous","Last Quarter","Waning Crescent" };
  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,ThisPhaseLabel[ThisPhase]);
  pingSerial();
  
  // MoonLight %
  y=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();

  // Vortech Mode
  y=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 (vtechmode == 0) ReefAngel.LCD.DrawLargeText(COLOR_LIMEGREEN,255,t,y,"Constant");
  else if(vtechmode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,t,y,"Lagoon");
  else if (vtechmode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD,255,t,y,"Reef Crest");
  else if (vtechmode == 3) ReefAngel.LCD.DrawLargeText(COLOR_RED,255,t,y,"Short Pulse");
  else if (vtechmode == 4) ReefAngel.LCD.DrawLargeText(COLOR_CORNFLOWERBLUE,255,t,y,"Long Pulse");
  else if (vtechmode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,t,y,"Smart NTM");
  else if (vtechmode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA,255,t,y,"Tidal Swell");
  else if (vtechmode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE,0,t,y,"Night");
  pingSerial();
  
  // Relays
  byte TempRelay = ReefAngel.Relay.RelayData;
  TempRelay &= ReefAngel.Relay.RelayMaskOff;
  TempRelay |= ReefAngel.Relay.RelayMaskOn;
  ReefAngel.LCD.DrawOutletBox(12, 103, TempRelay);
  pingSerial();
  
  // Date+Time
  ReefAngel.LCD.DrawDate(6, 122);
  pingSerial();
}

void DrawCustomGraph()
{
}


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller
    ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));
    // Ports toggled in Feeding Mode
    ReefAngel.FeedingModePorts = 0;
    // Ports toggled in Water Change Mode
    ReefAngel.WaterChangePorts = Port1Bit | Port2Bit;
    // Ports toggled when Lights On / Off menu entry selected
    ReefAngel.LightsOnPorts = Port4Bit | Port7Bit ;
    // Ports turned off when Overheat temperature exceeded
    ReefAngel.OverheatShutoffPorts = Port6Bit;
    // Use T1 probe as temperature and overheat functions
    ReefAngel.TempProbe = T1_PROBE;
    ReefAngel.OverheatProbe = T1_PROBE;
    // Set the Overheat temperature setting
    InternalMemory.OverheatTemp_write( 820 );

    // Ports that are always on
    ReefAngel.Relay.On( Return );
    ReefAngel.Relay.On( Vortech );
    ReefAngel.Relay.On( UPS );
    
    ////// Place additional initialization code below here
    
    // Reset Vortech to preferred defaults
    InternalMemory.RFMode_write(vtechDefault);
    InternalMemory.RFSpeed_write(vtechSpeed);
    InternalMemory.RFDuration_write(vtechDuration);
    ReefAngel.RF.UseMemory=true;
   
    ////// Place additional initialization code above here
}

void loop()
{
    ReefAngel.Relay.DelayedOn( Skimmer,5 );
    ReefAngel.StandardHeater( Heater,788,792 );
    
    ////// Place your custom code below here

    // Set Sunrise and Sunset from MHOCKIN Weather package functions. 
    if (dow!=day()) // used to see that were in a new day and need to recalculate sunrise and sunset
    {
      CalSun();
      dow=day();
      // And let's reset the light schedule
    } 

    ReefAngel.StandardLights( WhiteLEDs,SunriseHour+1,SunriseMin,SunsetHour-3,SunsetMin );
    ReefAngel.StandardLights( BlueLEDs,SunriseHour,SunriseMin,SunsetHour,SunsetMin );
    
    int currTime = hour()*100+minute();
    int riseTime=SunriseHour*100+SunriseMin;
    int setTime=SunsetHour*100+SunsetMin;
    
    if (( currTime <= riseTime ) || ( currTime >= setTime )) isNight=true; else isNight=false;
    
    if (isNight) 
    {
      ReefAngel.PWM.SetDaylight( MoonPhase() );
      ReefAngel.PWM.SetActinic( MoonPhase() );
      ReefAngel.Relay.On( Refugium );
      
      if (ReefAngel.RF.UseMemory && ReefAngel.RF.Mode!=Night) { 
        ReefAngel.RF.SetMode(Night,15,0);
      }
    } else { 
      ReefAngel.PWM.SetDaylight( 0 );
      ReefAngel.PWM.SetActinic( 0 );
      ReefAngel.Relay.Off( Refugium );

      // It's Daytime!
      if (ReefAngel.RF.UseMemory && ReefAngel.RF.Mode==Night) {
          ReefAngel.RF.SetMode(Feeding_Stop,0,0); //Temp fix for coming out of Night mode
          ReefAngel.RF.SetMode(vtechDefault,15,0);
      }
    }   

    if (ReefAngel.DisplayedMenu==FEEDING_MODE) bFeeding=true;
    if (ReefAngel.DisplayedMenu==DEFAULT_MENU && bFeeding )
    {
      bFeeding=false; 
      ReefAngel.RF.UseMemory=false;
      ReefAngel.RF.SetMode(Smart_NTM,100,5);
      ReefAngel.Timer[4].SetInterval(1800); // Timer for 30min
      ReefAngel.Timer[4].Start();
      vtechmode = Smart_NTM;
    }
    if (ReefAngel.DisplayedMenu==DEFAULT_MENU && ReefAngel.Timer[4].IsTriggered()) 
    {
      ReefAngel.RF.UseMemory=true;
    }

    if (ReefAngel.RF.UseMemory) { 
      vtechmode=ReefAngel.RF.Mode;
    }
    
    ////// Place your custom code above here

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

int JulianDate(int d, int m, int y)
{ 
  int mm, yy;
  int k1, k2, k3;
  int j;

  yy = y - (int)((12 - m) / 10);
  mm = m + 9;
  if (mm >= 12)
  {
    mm = mm - 12;
  }
  k1 = (int)(365.25 * (yy + 4712));
  k2 = (int)(30.6001 * mm + 0.5);
  k3 = (int)((int)((yy / 100) + 49) * 0.75) - 38;
  // 'j' for dates in Julian calendar:
  j = k1 + k2 + d + 59;
  if (j > 2299160)
  {
    // For Gregorian calendar:
    j = j - k3; // 'j' is the Julian date at 12h UT (Universal Time)
  }
  return j;
}

double MoonAge(int d, int m, int y)
{ 
  int j = JulianDate(d, m, y);
  //Calculate the approximate phase of the moon
  int ip = (j + 4.867) / 29.53059;
  ip = ip - abs(ip); 
  //After several trials I've seen to add the following lines, 
  //which gave the result was not bad 
  if(ip < 0.5)
    int ag = ip * 29.53059 + 29.53059 / 2;
  else
    int ag = ip * 29.53059 - 29.53059 / 2;
  // Moon's age in days
  byte ag = abs(ag) + 1;
  return ag;
}

byte MoonState(byte D)
{
  switch(D){
  case 1: 
    0, 29;
    ThisPhase = 0;
  case 2: 
    1, 2, 3, 4, 5, 6;
    ThisPhase = 1;
  case 3: 
    7;
    ThisPhase = 2;
  case 4: 
    8, 9, 10, 11, 12, 13;
    ThisPhase = 3;
  case 5: 
    14;
    ThisPhase = 4;
  case 6: 
    15, 16, 17, 18, 19, 20, 21;
    ThisPhase = 5;
  case 7: 
    22;
    ThisPhase = 6;
  case 8: 
    23, 24, 25, 26, 27, 28;
    ThisPhase = 7;
  default: 
    return 0;
  }
}

//Start of sunrise, sunset and cloud calculations- runs on reset and once a day thereafter.
void CalSun(){
   //Serial.println("CalSun Run Now");

       // latitude=dmsToSeconds(40,44,00);//United States of America- Salt Lake City, local time is -7 hours GMT 
       // longitude=dmsToSeconds(-111,47,00);
       latitude=dmsToSeconds(-18,17,9);// Great Barrier Reef, Australia
       longitude=dmsToSeconds(147,41,59); // Great Barrier Reef, Australia
       //**********************ok now were done changing things IF YOU CHANGED the Top part of the GLOBAL variable decleration AND this... your FULLY configured and ready to load******************************************** 
   
    if (dow==0){//if the controller has resarted we need to find midnight
      long hours, minutes;//store current elapsed local hours as total seconds from midnight
      time_t t=now();//store current clock time to parse
      hours=hour(t);
      hours=(hours*3600);//current hour number 0-23 as seconds
      minutes=minute(t);
      minutes=(minutes*60);//minutes of current hour as seconds
      newDay=now();
      newDay-=(hours+minutes);//Convert current local unix epoch time to local unix epoch time of midnight
    }
    else if (dow!=0){//if we did not restart but the day is new then it is midnight and were good to go..
      newDay=now();
    }
      
    //#define SECS_YR_2000 (946684800) the time at the start of y2k (need to subtract from unix epoch time to bring to Y2K origin
    newDay-=946684800;//convert GMT unix Epoch to seconds elasped since 2000 for GMT midnight of today
    
    rise=newDay;//set value to send to SunRise as midnight GMT in seconds from Y2K
    set=newDay;//
    //Calculate rise time and set time using Epherma Library functions (see end of code) 
    SunRise(&rise);//call to Epherma function
    SunSet(&set);//Call to Epherma functionunsigned long newDay;
  
   // Convert rise/set to normal hours/minutes before we make it an offset from midnight. 
   // Currently correcting for a 56 minute correction to Sunrise and -19 minute correction to Sunset (8/14/2012@GBR)
   SunriseHour=hour(rise+UserOffset);
   SunriseMin=minute(rise+UserOffset);
   SunsetHour=hour(set+UserOffset);
   SunsetMin=minute(set+UserOffset);

   /*Serial.print("rise and set=  ");
   Serial.println(rise);
   Serial.println(set);
   Serial.print("newDay as seconds since 2000 to todays midnight=  ");
   Serial.println(newDay);*/
   rise=(rise-newDay);// set to elapsed seconds of day
   set=(set-newDay);
   /*Serial.print("rise and set as elapsed seconds of day=  ");
   Serial.println(rise);
   Serial.println(set);*/
   newDay+=946684800;//Convert newDay back to unix epoch GMT midnight today (used in loop to determine how far we are into the day) 
   /*Serial.print("newDay as seconds since since 1970 to todays midnight=  ");
   Serial.println(newDay);
   Serial.print("elapsed is");
   long elapsed=now()-newDay;
   Serial.println(elapsed);*/
   
}//END SunCalc FUNCTION

//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.  
//this code is being used freely with attribution to Micahel Rice in accord with his request
//  A big thank you for these library functions.  Its great! 

// decimal degrees
long ddToSeconds(float dd){
   return dd * 3600.0;
}

//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;

   ret = labs((long)d);
   ret = ret * 3600L + 60L * m + s;
   ret = (d<0L) ? -ret : ret;
   return ret;
}
/* ------------------------------------------------------------------------------------------------
   'Equation of Time'
   We use the 'short form equation, which has a theoretical accuracy of about 40 seconds.
   The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;

   dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
   dt %= _tropical_year;
   t = dt;
   t /= _tropical_year;
   t *= 6.283185307179586;
   t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
   return t;
}

/* -----------------------------------------------------------------------------------------------
   'Solar Declination'
   Returns declination in radians
   Accurate to within 50 arc-seconds
*/
double SolarDeclination(unsigned long dt){
double y;

   dt %= _tropical_year;
   y = dt;
   y /= _tropical_year; // fractional year
   y *= 6.283185307179586;
   y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
   return y;
}

/* ------------------------------------------------------------------------------------------------
   Return the period between sunrise and sunset, in seconds.
   At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;

   d = -SolarDeclination(dt); // will be positive in Northern winter
   l = latitude / _sec_rad; // latitude in radians

   e += 60.0 * l * tan(l + d); // latitudinal error
   d = tan(l) * tan(d); //

   if(d>1.0) return 86400UL;
   if(d < -1.0) return 0UL;

   d = acos(d);
   d /= _zenith;

   n = 86400UL * d;
   n += e;
   return n;
}


/* ------------------------------------------------------------------------------------------------
   Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
   Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
   returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
   all day.

*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;

   daylen = daylightseconds(*dt);
   if(daylen == 86400UL) return 1;   // there is no 'night' today (midnight sun)
   if(daylen == 0UL) return -1; // there is no 'day' today

   *dt /= 86400UL;
   *dt *= 86400UL;
   *dt += 43200UL; // set the time stamp to 12:00:00 GMT

   *dt -= daylen / 2; //        sunrise at the prime meridian
   if(set) *dt += daylen; //     sunset at the prime meridian

   *dt -= equation_of_time(*dt);

   //*dt -= longitude / 15.0; // rotate to our own meridian

   return 0;
}

// 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
    return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
    return SunRiseSet(when, 1);
}
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: Sunrise / sunset code based on location

Post by rufessor »

Hi- Glad to hear that its working. With respect to the errors, I *think* I used to get much better accuracy... but I was doing things wrong with time and I figured this out... so I will explain exactly how this is working.... I dont see where an error is creeping in but maybe you can help as your the first person to play with this who seems to know how to code a little bit...
Basically, arduio uses the unix epoch and keeps track of time as total elapsed seconds since the year 1970. The sun rise and set algorithim requires this to be seconds since the year 2000 so I always subtract the interveining # of seconds from system time.


Since every arduino processor treats time as if the processor was in fact on GMT time, i.e. it takes local 6 pm and just assumes that local pm is the same time as GMT 6pm... To make my code easier to use (and since I was screwing this up and it was killing my program for time zones ahead of GMT) I basically choose to just go ahead and let it run with local time as GMT and feeding that into the sunrise and sunset algorithim- since its using the correct elapsed seonds since 1970 it should be fine. If this is hard to follow.. imagine this. You live in a time zone that is GMT -1 hours and there is no daylight savings. Your arduino processor keeps track of the number of seconds since 1970 but if you were to compare it to the actual value LOCALLY... which is 3600 seconds BEHIND GMT.. your processor is wrong, its always 3600 seconds ahead but shows the right time, e.g. it just thinks its in a time zone that has no offset from GMT but the hour and minute and second are set to match your watch wherever you live.

The way I understand the algorithm to work is basically as follows...

The number of seconds since 2000 is used to calculate the solar declination which is the angle of the sun is offset from the equator, this is required to calculate how many seconds there are in the day... then to calculate rise it basically subtracts half this amount from noon GMT and then subtracts the anthelma (equation of time) which basically accounts for the change in time of daylight as the sun moves throughout the year (complicated to explain, but if you put a stake in the ground and make a point on the ground a noon every day for a year you get a slightly odd shaped figure 8)...

Then finally, its takes the elapsed seconds since 2000 (having added in seconds from midnight for rise, and seconds since midnight for set) with the corrections... and it subtracts this value from the number of seconds since 2000.

Longitude (in total seconds of longitude)/15

What this does, is move the sunrise and sunset times for your latitude at the 0 longitude and converts it to local time. The earth of course rotates 360 degrees/24 hours which is 15 degrees/hour. So, if you divide your latitude as total seconds of latitude by 15 you get a time correction for the lenght of time it takes the planet to rotate around from 0 latitude to your position.


I guess I dont quite fully get why the errors are so large.... we can talk more about this but when I was treating time incorrectly, I SWEAR I had better accuracy..... if could just be that as the season was changing and my days were getting towards the max length the error creeped up on me and I started to notice it coincidently as I made the change to correct my error and became paranoid about it working correctly so probably started to watch dog the tank again and then saw the error. I think I know that the error is going to be the greatest at summer and winter solstice and were pretty much there now... so I guess we can watch and see.. but if you want to play we can work on this and see if there is a subtle error in the way this is working.

Congrats on the convert... so basically your using this to run on the main, without the weather effects! Very good port, but you should get a dimming module... the weather effects are where its AT :lol: :lol: :lol: :lol: :lol: :mrgreen:
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Thanks for the details. I'll do another re-read of your code with your explanation, see if I can see what's going on. I think you're right though in that the error is largest closest to the solstice because when I plugged in january, the times looked much closer from what I remember. Do you have what you changed at all? Maybe we could go back and test with that...

And yes, I'm running on the main since I do not have the dimming module and am currently without seperate dimming control LEDs. I'm sure the weather is nice... one day when I have money for and when LED manufacturers come out with better fixtures or decide to do a DIY fixture... :)
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: Sunrise / sunset code based on location

Post by rufessor »

What I changed was or should be inconsequential. In fact, I was correcting an error. I had not fully understood the fact that arduino (I have no idea if this is generally true of all computers) processors keep track of the hours and minutes as local time (without and DST correction) but frame it as GMT in terms of the elapsed seconds since 1970 or whatever reference point you want to use. So... I had been correcting my processor time to bring it to GMT time (which of course is an error....). I can add another thing.

The algorithm as written by michael rice and that was clearly adapted from the NOAA sunrise and sunset algorithm which was from yada yada... basically, Michael used the least accurate algorithm because the Arduino processor is VERY weak in floating point math... so its got loads of baked in inaccuracy from a theoretical aspect. I suspect that at solstice +/- 20 mins or so (probably also dependent on your longitude as it gets bigger I suspect the error will grow) is probably all that it can do period. I kinda decided I didn't care, its a fish tank- I mean really. I do note that my tank is MUCH more accurate now than it was even 3 weeks ago so its acting like the error is shrinking pretty fast as our day lengths come down.

Anyhow, I kinda doubt there are any errors in the way I am doing this, I wanted to explain how it works as you have adopted it and now you have the information. I especially like the divide latitude by 15... pretty cool. Obvious if you think about it, but cool none the less.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Cool. If +/- 20 mins is the worst case at the solstice, than I'm fine with it. Like you said it's a fish tank :) I'm happy to be able to reproduce the variable length days that a coral would experience in a natural environment. I'm sure the coral's don't really care, but I get to say to others how cool it is :)

The code is pretty simple looking through it, so probably best to leave it alone. I think there's a little bit more to clean up and I look forward to seeing it Class'ifyed by binder. I think that would make it really easy to use for others.
User avatar
DrewPalmer04
Posts: 818
Joined: Tue May 29, 2012 2:12 pm
Location: Christopher, IL

Re: Sunrise / sunset code based on location

Post by DrewPalmer04 »

Any possibilities to do this with stock dimming channels? I would include this code now to sync the T5s
Out for now...but not over.

VISIT: Ethernet Module/Wifi Alternative
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Drew, you could just use the times I'm getting to start your PWM slope on the regular dimming channels, or you could add the function for SolarNoon that I stripped out (since I didn't need it) and re-integrate the custom slope that rufessor was using...

Easiest way would be I think to grab the times I'm using for sunrise/sunset and just apply that with your on/off slopes.. I haven't done anything with the dimming so if there are other steps involved, I can let others chime in.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

rufessor wrote: I especially like the divide latitude by 15... pretty cool. Obvious if you think about it, but cool none the less.
rufessor... I'm wondering if this could be part of the problem...

From the code you posted... this rotation is commented out...
rufessor wrote:

Code: Select all

   //*dt -= longitude / 15.0; // rotate to our own meridian
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Wow, so I just tested with that uncommented, and my time shot halfway around the world... Sunrise and Sunset was suddenly way off the mark (guess what... like 14 hours off the mark...) which is the exact difference between EDT and the +10 hours at the GBR. I changed my offset from +5 hours to -9 hours and bam, back to basically where I was... I was all strange for a bit until I remembered what you said about the Arduino thinking whatever local time is GMT. So if we were calculating at the prime meridian and never rotating to the right spot...

After playing with the dates and seeing what the offsets are now... my error deviation is now -11 rise,-7 minutes set. Playing with the dates and trying different solstices, it switched around to around -7 minutes / around -11 minutes, and so forth.. I didn't record the variations. But basically. I bet if we found the average of the differences and subtracted it would be spot on... I'm sure though if you changed the coordinates the error deviation would be different. But it seems like this were not rotating to the proper longitude. Either way, the times look good. I'm going to leave it like this for now, unless there is some other reason this was commented out.
Last edited by lnevo on Fri Aug 17, 2012 10:03 pm, edited 1 time in total.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Here's the updated stripped out code. I cleaned up a tiny bit and moved newDay to a local variable since it was only used in that function. I also moved the lat and long assignment to the global declaration and did it in decimal degrees. Then in Calsun() I used ddToSeconds instead of dmsToSeconds. Doing this brought me down to a -5/-7 error...wow! I'm using Queensland, Australia to check time, I bet if I set my coords to that exactly, I'd be even closer! Anyway, here it is.

binder, you should use this as there's less variables defined and easier to setup now :)

Code: Select all

//By Matthew Hockin 2012.  
//SWFLTEK library functions, #includes, and #define were copied directly from the Epherma library
//Epherma (SWFLTEK.com) was written by Michael Rice- thanks Michael it works great. 
//If you copy from this (its all open source) please 
//acknowledge Michael for SWFLTEK library code (obviously labeled) or Matthew Hockin (for the rest).

#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 <InternalEEPROM.h>
#include <RA_Colors.h>
#include <RA_CustomColors.h>
#include <Salinity.h>
#include <RF.h>
#include <IO.h>
#include <ORP.h>
#include <AI.h>
#include <ReefAngel.h>

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

//includes for SWFLTEK functions
#include <stdlib.h>
#include <math.h>
//defines for SWFLTEK functions
#define _sec_rad 206264.806247096370813 // arc-seconds per radian
#define _tilt 0.409092804222329 // axial tilt of earth at epoch, in radians
#define _tropical_year 31556925 // tropical year in seconds... rounding error accumulates to 26 seconds by the year 2136
#define _zenith 3.11250383272322 // 'zenith' of rising (setting) sun in radians (360 - 2 * 90.833 degrees)

//*******************GLOBAL VARIABLE DECLERATIONS FOR MHOCKIN Weather package*************************************
long latitude=-18.285833; // Great Barrier Reef, Australia
long longitude=147.699722; // Great Barrier Reef, Australia
byte dow=0;//day of week
//*******************END HEADER FOR MHOCKIN Weather package*******************************************************

// Additional variables added for Sunrise/Sunset calculation
int SunriseHour, SunriseMin, SunsetHour, SunsetMin;
int userOffset=-(9*3600); // -9 hours

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


void setup()
{
    // This must be the first line
    ReefAngel.Init();  //Initialize controller

    // Ports that are always on
    
    ////// Place additional initialization code below here
       
    ////// Place additional initialization code above here
}

void loop()
{
    ////// Place your custom code below here

    // Set Sunrise and Sunset from MHOCKIN Weather package functions. 
    if (dow!=day()) // used to see that were in a new day and need to recalculate sunrise and sunset
    {
      CalSun();
      dow=day();
      // And let's reset the light schedule
    } 
    
    ////// Place your custom code above here

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


//Start of sunrise, sunset and cloud calculations- runs on reset and once a day thereafter.
void CalSun(){
long newDay; 
unsigned long rise;//time in seconds from the year 2000 (GMT) for sunrise
unsigned long set;//time in seconds from the year 2000 (GMT) for sunrise

  //Serial.println("CalSun Run Now");
  
  latitude=ddToSeconds(latitude);
  longitude=ddToSeconds(longitude);
  
  if (dow==0){//if the controller has resarted we need to find midnight
    long hours, minutes;//store current elapsed local hours as total seconds from midnight
    time_t t=now();//store current clock time to parse
    hours=hour(t);
    hours=(hours*3600);//current hour number 0-23 as seconds
    minutes=minute(t);
    minutes=(minutes*60);//minutes of current hour as seconds
    newDay=now();
    newDay-=(hours+minutes);//Convert current local unix epoch time to local unix epoch time of midnight
  } 
  else if (dow!=0){//if we did not restart but the day is new then it is midnight and were good to go..
    newDay=now();
  }
      
  //#define SECS_YR_2000 (946684800) the time at the start of y2k (need to subtract from unix epoch time to bring to Y2K origin
  newDay-=946684800;//convert GMT unix Epoch to seconds elasped since 2000 for GMT midnight of today
    
  rise=newDay;//set value to send to SunRise as midnight GMT in seconds from Y2K
  set=newDay;//
  //Calculate rise time and set time using Epherma Library functions (see end of code) 
  SunRise(&rise);//call to Epherma function
  SunSet(&set);//Call to Epherma functionunsigned long newDay;
  /*
  Serial.print("rise and set=  ");
  Serial.println(rise);
  Serial.println(set);
  Serial.print("newDay as seconds since 2000 to todays midnight=  ");
  Serial.println(newDay);
  */
  
  // Convert rise/set to normal hours/minutes before we make it an offset from midnight. 
  // Currently correcting for a 56 minute correction to Sunrise and -19 minute correction to Sunset (8/14/2012@GBR)
  SunriseHour=hour(rise+userOffset);
  SunriseMin=minute(rise+userOffset);
  SunsetHour=hour(set+userOffset);
  SunsetMin=minute(set+userOffset);
  
}//END SunCalc FUNCTION

//********************** DO NOT MESS WITH THIS UNLESS YOU KNOW WHAT YOUR DOING****************************
//THE CODE BELOW THIS copied directly from the SWFLTEK Epherma library constructed by Michael Rice.  
//this code is being used freely with attribution to Micahel Rice in accord with his request
//  A big thank you for these library functions.  Its great! 

// decimal degrees
long ddToSeconds(float dd){
  return dd * 3600.0;
}

//Degrees, minutes, seconds
long dmsToSeconds(int d, unsigned char m, unsigned char s){
long ret;

  ret = labs((long)d);
  ret = ret * 3600L + 60L * m + s;
  ret = (d<0L) ? -ret : ret;
  return ret;
}
/* ------------------------------------------------------------------------------------------------
   'Equation of Time'
   We use the 'short form equation, which has a theoretical accuracy of about 40 seconds.
   The returned value is in seconds.
*/
int equation_of_time(unsigned long dt){
double t;

  dt -= 192540UL; // refer to Jan 3 2000 05:29 (first periapsis)
  dt %= _tropical_year;
  t = dt;
  t /= _tropical_year;
  t *= 6.283185307179586;
  t = -459.27672 * sin(t) + 575.333472 * sin(2.0 * t + 3.588414);
  return t;
}

/* -----------------------------------------------------------------------------------------------
   'Solar Declination'
   Returns declination in radians
   Accurate to within 50 arc-seconds
*/
double SolarDeclination(unsigned long dt){
double y;

  dt %= _tropical_year;
  y = dt;
  y /= _tropical_year; // fractional year
  y *= 6.283185307179586;
  y=0.006918-0.399912*cos(y)+0.070257*sin(y)-0.006758*cos(y*2)+0.000907*sin(y*2)-0.002697*cos(y*3)+0.00148*sin(y*3);
  return y;
}

/* ------------------------------------------------------------------------------------------------
   Return the period between sunrise and sunset, in seconds.
   At high latitudes around the time of the solstices, this could be zero, or all day.
*/
unsigned long daylightseconds(unsigned long dt){
float l, d, e;
long n;

  d = -SolarDeclination(dt); // will be positive in Northern winter
  l = latitude / _sec_rad; // latitude in radians

  e += 60.0 * l * tan(l + d); // latitudinal error
  d = tan(l) * tan(d); //

  if(d>1.0) return 86400UL;
  if(d < -1.0) return 0UL;

  d = acos(d);
  d /= _zenith;

  n = 86400UL * d;
  n += e;
  return n;
}


/* ------------------------------------------------------------------------------------------------
   Modify the passed time stamp to the time of sunrise (or sunset if 'set' is non-zero).
   Returns 0 to signal 'normal' completion. If the position is in a polar circle, 1 will be
   returned if the sun is above the horizon all day, and -1 if the sun is below the horizon
   all day.

*/
char SunRiseSet(unsigned long * dt, char set){
unsigned long daylen;

  daylen = daylightseconds(*dt);
  if(daylen == 86400UL) return 1;   // there is no 'night' today (midnight sun)
  if(daylen == 0UL) return -1; // there is no 'day' today

  *dt /= 86400UL;
  *dt *= 86400UL;
  *dt += 43200UL; // set the time stamp to 12:00:00 GMT

  *dt -= daylen / 2; //        sunrise at the prime meridian
  if(set) *dt += daylen; //     sunset at the prime meridian

  *dt -= equation_of_time(*dt);

  *dt -= longitude / 15.0; // rotate to our own meridian

  return 0;
}

// 'short' forms of SunRiseSet
char SunRise(unsigned long* when){
  return SunRiseSet(when, 0);
}
char SunSet(unsigned long* when){
  return SunRiseSet(when, 1);
}
binder
Posts: 2871
Joined: Fri Mar 18, 2011 6:20 pm
Location: Illinois
Contact:

Re: Sunrise / sunset code based on location

Post by binder »

lnevo wrote: binder, you should use this as there's less variables defined and easier to setup now :)
Ok. I'll save this code to use to start splitting up. Haven't started yet....been working on a few new features for the android app such as Automatic Updating the device and Automatically configuring the modules in the device based on the settings from the controller. :)
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

OK. FYI I had an odd moment last night, wife came in at 1:30am and asked why is the fishtank bright? I checked, and all the lights were on... I turned them off and did some debugging today.. can't reproduce it. Need to play around with date and see if rollover at midnight is working properly...
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Very frustrated right now.... I could really use someone else to take a look at the code and test out with different days. I've reverted my code back even to a few days back and same behavior... After last night when the lights came back on after midnight, I've been troubleshooting all day and can't find what is going on. Now when I debug every time I change the day I get wacky times and can't reproduce a consistent day/time. I'm defaulting now back to a static rise/set, but not happy with it..

Anyway, use at your own risk right now...
rimai
Posts: 12881
Joined: Fri Mar 18, 2011 6:47 pm

Re: Sunrise / sunset code based on location

Post by rimai »

Could it be the newDay check?

Code: Select all

  if (dow==0){//if the controller has resarted we need to find midnight
dow would be 0 on Sunday, right?
Why are you deducting seconds from midnight only when dow==0?
Roberto.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

This was from rufessor's code. He sets dow=0 when the controller reboots to force the check... I wonder if it was all because yesterday was Sunday though... it was working ok afaik during the week... I'll play with it later tonight then and see if it wasn't just something stupid then.. Could probably get away with setting dow=-1 initially to force the setup.

Honestly in the test versions I was working with I took out that check altogether since why should it matter that it's midnight or during the day when you do the calculation, but was still seeing issues...Was a very frustrating night of coding, especially since I got sidetracked with my portal issue...
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Re: Sunrise / sunset code based on location

Post by lnevo »

Thanks rufessor. All your info is extremely helpful. I can imagine it's frustrating helping to debug such a complex code, especially when it's working for you :) I have not had a chance to get back to the troubleshooting and it may be some days before I get to. I do not have any time/sync automatic setup or anything, but was using the Date/Time setup screen to update the rise/set time. It would jump around to all kinds of times and be a bit crazy, which was definitley not the case when I was debugging before which is why the Sunday bit may be something to consider. I did however try taking all the dow() code out and just having it CalcSun() every time during the loop, but i went through so many iterations debugging, I have no clue. I was mainly afraid that I stripped something out inadvertantly but even jumping back to older code versions when I know it was giving more accurate times was wierd...

And for the record when I say crazy times, I mean gong from 11:18->23:01 to 18:46->10:08 to 10:46->8:15

Past troubleshooting was not going all wacky like that... With the rotation code, the main thing I noticed was the offset seemed to match the UTC-10 hours from GBR time and as I said, the offsets from real sunrise/sunset were under 12 minutes for both, whereas without that I was +50/-20 minutes or something like that...

The main thing is me getting my head clear enought to attempt to debug again. I'd like to setup a timer in the loop so I can increment a day every 30 seconds and CalcSun() and spit out the times in serial output so I dont' have to keep grabbing and playing with the joystick... this would help trememndously...
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: Sunrise / sunset code based on location

Post by rufessor »

Thats not quite correct...

I use dow=day();

which is 1-31

so it will never be 0.

I don't understand what happened to you with the lights on.

However, if your doing any kind of external time syncing I would look at that as a used on the forum for my entire code is having serious issues with this setting time to decades in the future randomly. I think they gave up... unsure why as its clearly a time sync issue for them and not my code, but everyone has a point where they move on :)

Even if day() gave a zero value, which is impossible according to the arduino time.h documentation, it should still run, what would occur is a recalculation of sunrise and sunset for the day every time the loop ran, but it would use the reset loop and correct to midnight for the current time and should thus give you correct rise and set values so the only thing that would really happen, is you would eat compute cycles.

I will look at your code when I have the time.
rufessor
Posts: 293
Joined: Tue Oct 25, 2011 7:39 am

Re: Sunrise / sunset code based on location

Post by rufessor »

Sorry... I had posted a whole crap load of BUCK... forgetting how I had done this... and I think we posted simultaneously... so my response above was written to correct (I deleted three dumb posts!) my prior lunacy.

I stand with the above statement and will look at your code as I said! Please remember, I literally taught myself how to code c++/arduino to write that, so although its working for a few people (I have no idea who is using this but can confirm 2 + your experience generally validating it with above issues you documented). Its seemingly innocent (the issue were coding) but put me through some serious troubles. It does however work under the conditions I am running... I will look at yours!

Yeah, the rotation thing, I think *think* I removed that because of the way we account for time, which makes sense - but it is painful given what were doing.
I think I will work on coding in offsets- beware, this was a serious head ache but like I had said in my prior deleted posts, I think the head ache I had was self induced as that was all prior to the version your using and when I didn't understand how time is accounted on arduino.

Its seemingly SO SIMPLE... but its totally mind bending- I still cannot fully conceptualize the whole removing rotation but using local as GMT and then getting the right time... its just stupid, but if it mind bends you- your not alone!!

I am super psyched your working on this for other people who do not have dimming modules... it will be used... also happy to see people like you taking what I tried to do and spending time working it though, its the only way this is ever going to be fully fleshed out. So FAR... I am looking for errors outside of the code I wrote as its hard to believe its not going to be correct for you when its SO stable for me and at least one other person.

I wish we could get an accurate count on who, if anyone besides myself and jneuhausen (wrong spelling) in south africa are using this, it would be very nice to KNOW that its solid and not working only on two systems... but then- if thats indeed the case--- why would it not work on EVERY system?????

I am as confused as you, code will tell... I will post when I get a chance to REALLY go though yours!

OH.. as for the rotation thing... its really super easy to predict what will happen.

Just take hours*3600 + mins*60 + seconds of latitude as your input into the array- divide this by 15 and thats the seconds it will be offset (if you uncomment) so your getting pretty much exactly the correct offsets for the ACTUAL GMT rise and set given the coordinates your using (using my head and glancing at the code). BUT... we use LOCAL as GMT and the it spits back the GMT rise and set... assuming we fed it actual GMT but we did not. So... I think, if you consider that its calculating GMT rise and set and then simply correcting for the earths rotation, and since our system thinks its GMT, we don't need to correct which is why its commented out, but to be honest, its been over a month and I now forget even commenting that out, but I did... cause its not in the original code only in the distribution.

I am still unsure if you think that the times were MORE accurate when you corrected to true GMT.... I have this feeling that it will be more accurate if we do that, but like I said, I bailed out on that part due to misconceptions I had and simply MADE it work using local as GMT direct from the processor.

I still cannot quite conceptualize any error creeping in if were feeding it the true unix time for GMT midnight, it should be identical since when I put in my midnight time from my system it comes back as TRUE GMT midnight on the unix epoch time server, so there should be no difference but for requiring the /15 correction if we use true local time... so it seems like I have it correct and I dont think error should arrise... let me know.

Please PM with Q's you need help with fast.
User avatar
lnevo
Posts: 5430
Joined: Fri Jul 20, 2012 9:42 am

Sunrise / sunset code based on location

Post by lnevo »

Hehe...no worries. I'm sure we will get it all worked out...it wouldn't be fun if it weren't mind bending :)

I think once I can get a debug loop going it will help male this go faster, it just takes too much time to upload, fiddle with date/time, compare, etc...

Also, I'm going to try and convert to standalone binary without arduino involved and see if I get similar results. If I can see this running at a command line, will def speed things up!
Post Reply