I'm trying to create a custom main screen. I have read the pdf Curt created and feel like I have a really good handle on how to use it. My problem is that when I compile and upload to my RA, the fonts used don't seem to be the fonts I indicated.
As an example, I'll take the simplest line to see my point. I'm trying to write "Current USA Cardiff Reef" at the top of the screen in the default font. By my count this is 24 characters long. At 5 pixels wide per character that gives me a total length of 120 pixels. If I position this at x=5, y=2 I feel like it should be centered on the screen at the top of the screen.
In practice when I compile and upload my pde, the text runs off the screen. I see "Current USA Cardiff R". Can someone tell me what I am missing here?
Also, please note that when I attemp to use the 12x16 font, it appears to me that it is 16x16 when displayed on the screen.
I know that I'm probably doing something monumentally stupid, so I've attached my current PDE file below and a picture of my hideous screen.
Code: Select all
// This version designed for development libraries v0.8.5 Beta 17 or later
/* The following features are enabled for this PDE File:
#define DisplayImages
#define DisplayLEDPWM
#define CUSTOM_MENU
#define CUSTOM_MENU_ENTRIES 3
#define CUSTOM_MAIN
#define FONT_8x8
#define NUMBERS_12x16
#define AI_LED
*/
//The following files are included when compiling:
#include <ReefAngel_Features.h>
#include <ReefAngel_Globals.h>
#include <ReefAngel_Colors.h>
#include <ReefAngel_Wifi.h>
#include <Wire.h>
#include <OneWire.h>
#include <Time.h>
#include <DS1307RTC.h>
#include <ReefAngel_EEPROM.h>
#include <ReefAngel_NokiaLCD.h>
#include <ReefAngel_ATO.h>
#include <ReefAngel_Joystick.h>
#include <ReefAngel_LED.h>
#include <ReefAngel_TempSensor.h>
#include <ReefAngel_Relay.h>
#include <ReefAngel_PWM.h>
#include <ReefAngel_Timer.h>
#include <ReefAngel_Memory.h>
#include <ReefAngel_AI.h>
#include <ReefAngel.h>
//Begin Initalize Variables
byte WhiteValue=0;
byte BlueValue=0;
byte RoyalBlueValue=0;
byte vtechmode=1;
byte DayAge;
byte ThisPhase;
int JulianDate(int,int,int);
double MoonAge(int,int,int);
byte MoonState();
boolean ForceCloud=false;
//End Initalize Variables
//Start of Custom Menu Section
prog_char menu1_label[] PROGMEM = "Feeding";
prog_char menu2_label[] PROGMEM = "Water Change";
prog_char menu3_label[] PROGMEM = "Thunder Storm";
PROGMEM const char *menu_items[] = {
menu1_label, menu2_label, menu3_label};
void MenuEntry1()
{
ReefAngel.FeedingModeStart();
}
void MenuEntry2()
{
ReefAngel.WaterChangeModeStart();
}
void MenuEntry3()
{
ReefAngel.LCD.DrawDate(6, 90);
ReefAngel.LCD.DrawText(DefaultFGColor, DefaultBGColor, 20, 40, "ThunderStorm");
ForceCloud=true;
}
//End of Custom Menu Section
//Begin of Function to Convert Numbers to Text Strings
void ConvertNumToString(char* string, int num, byte decimal)
{
char temptxt[3];
int Temp = num;
if (Temp==0xFFFF) Temp=0;
itoa(Temp/decimal,string,10);
if (decimal>1)
{
itoa(Temp%decimal,temptxt,10);
strcat(string, ".");
if (Temp%decimal<10 && decimal==100) strcat(string, "0");
strcat(string, temptxt);
}
}
//End of Function to Convert Numbers to Text Strings
//Start Custom Screen Display Section
void DrawCustomMain()
{
char text[7];
ConvertNumToString(text, ReefAngel.Params.Temp1, 10);
ReefAngel.LCD.DrawHugeText(COLOR_PURPLE, COLOR_WHITE, 2, 12, text, Num12x16);
ReefAngel.LCD.DrawText(COLOR_PURPLE, COLOR_WHITE, 51, 12, "degF tank temp");
ReefAngel.LCD.DrawText(COLOR_DARKORANGE, COLOR_WHITE, 9, 38, "the tank pH is");
ConvertNumToString(text, ReefAngel.Params.PH, 100);
ReefAngel.LCD.DrawHugeText(COLOR_DARKORANGE, COLOR_WHITE, 80, 30, text, Num12x16);
ReefAngel.LCD.Clear(COLOR_BLACK, 1, 47, 132, 47);
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 28, 49, "Room Temp:");
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.Params.Temp2, COLOR_DARKSEAGREEN, 82, 49, 2);
ReefAngel.LCD.Clear(COLOR_BLACK, 1, 58, 132, 58);
ReefAngel.LCD.Clear(COLOR_LIGHTGRAY, 1, 59, 132, 77);
ReefAngel.LCD.DrawText(COLOR_BLACK, COLOR_LIGHTGRAY, 10, 60, "Current EcoSmart Mode:");
//Begin EcoSmart Mode Display Section
if (vtechmode == 0) ReefAngel.LCD.DrawLargeText(COLOR_LIMEGREEN, COLOR_LIGHTGRAY, 33, 69, "Constant", Font8x8);
else if(vtechmode == 1) ReefAngel.LCD.DrawLargeText(COLOR_GOLD, COLOR_LIGHTGRAY, 41, 69, "Lagoon", Font8x8);
else if (vtechmode == 2) ReefAngel.LCD.DrawLargeText(COLOR_GOLD, COLOR_LIGHTGRAY, 25, 69, "Reef Crest", Font8x8);
else if (vtechmode == 3) ReefAngel.LCD.DrawLargeText(COLOR_CORNFLOWERBLUE, COLOR_LIGHTGRAY, 21, 69, "Short Pulse", Font8x8);
else if (vtechmode == 4) ReefAngel.LCD.DrawLargeText(COLOR_PINK, COLOR_LIGHTGRAY, 25, 69, "Long Pulse", Font8x8);
else if (vtechmode == 5) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA, COLOR_LIGHTGRAY, 1, 69, "Nutrient Trnsprt", Font8x8);
else if (vtechmode == 6) ReefAngel.LCD.DrawLargeText(COLOR_MAGENTA, COLOR_LIGHTGRAY, 21, 69, "Tidal Swell", Font8x8);
else if (vtechmode == 9) ReefAngel.LCD.DrawLargeText(COLOR_WHITE, COLOR_LIGHTGRAY, 45, 69, "Night", Font8x8);
//End EcoSmart Mode Display Section
ReefAngel.LCD.Clear(COLOR_BLACK, 1, 78, 132, 78);
//Begin Moon Phase Display Section
DayAge = MoonAge(day(), month(), year());
MoonState(DayAge);
if (ThisPhase == 0)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 45, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 70, 89, "New");
}
else if (ThisPhase == 1)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 15, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 40, 89, "Waxing Crescent");
}
else if (ThisPhase == 2)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 20, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 45, 89, "First Quarter");
}
else if (ThisPhase == 3)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 16, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 43, 89, "Waxing Gibbous");
}
else if (ThisPhase == 4)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 41, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 68, 89, "Full");
}
else if (ThisPhase == 5)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 16, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 43, 89, "Waning Gibbous");
}
else if (ThisPhase == 6)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 21, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 47, 89, "Last Quarter");
}
else if (ThisPhase == 7)
{
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 15, 89, "Moon:");
ReefAngel.LCD.DrawText(COLOR_DARKSEAGREEN, COLOR_WHITE, 40, 89, "Waning Crescent");
}
//End Moon Phase Display Section
ReefAngel.LCD.Clear(COLOR_BLACK, 1, 98, 132, 105);
//Begin of AquaIllumination Channel Display Section
ReefAngel.LCD.DrawText(COLOR_WHITE, COLOR_BLACK, 5, 98, "W:");
if (WhiteValue < 10)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(0), COLOR_WHITE, 25, 98, 0);
}
else if (WhiteValue < 100)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(0), COLOR_WHITE, 20, 98, 0);
}
else if (WhiteValue == 100)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(0), COLOR_WHITE, 15, 98, 0);
}
ReefAngel.LCD.DrawText(COLOR_WHITE, COLOR_BLACK, 30, 98, "%");
ReefAngel.LCD.DrawText(COLOR_BLUE, COLOR_BLACK, 50, 98, "B:");
if (BlueValue < 10)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(1), COLOR_BLUE, 70, 98, 0);
}
else if (BlueValue < 100)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(1), COLOR_BLUE, 65, 98, 0);
}
else if (BlueValue == 100)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(1), COLOR_BLUE, 60, 98, 0);
}
ReefAngel.LCD.DrawText(COLOR_BLUE, COLOR_BLACK, 75, 98, "%");
ReefAngel.LCD.DrawText(COLOR_ROYALBLUE, COLOR_BLACK, 95, 98, "R:");
if (RoyalBlueValue < 10)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(2), COLOR_ROYALBLUE, 115, 98, 0);
}
else if (RoyalBlueValue < 100)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(2), COLOR_ROYALBLUE, 110, 98, 0);
}
else if (RoyalBlueValue == 100)
{
ReefAngel.LCD.DrawSingleMonitor(ReefAngel.AI.GetChannel(2), COLOR_ROYALBLUE, 105, 98, 0);
}
ReefAngel.LCD.DrawText(COLOR_ROYALBLUE, COLOR_BLACK, 120, 98, "%");
//End of AquaIllumination Channel Display Section
//Begin of Outlet Box and Date Display Section
byte TempRelay = ReefAngel.Relay.RelayData;
TempRelay &= ReefAngel.Relay.RelayMaskOff;
TempRelay |= ReefAngel.Relay.RelayMaskOn;
ReefAngel.LCD.DrawOutletBox(13, 107, TempRelay);
ReefAngel.LCD.DrawDate(10, 120);
//End of Outlet Box and Date Display Section
}
//End Custom Screen Display Section
//Begin custom Graph Function
void DrawCustomGraph()
{
ReefAngel.LCD.Clear(COLOR_BLACK, 1, 1, 132, 10);
ReefAngel.LCD.DrawText(COLOR_WHITE, COLOR_BLACK, 5, 2, "Current USA Cardiff Reef");
}
//End Custom Graph Function
//Begin Cloud and Thunderstorm Function
void CheckCloud()
{
// ------------------------------------------------------------
// Change the values below to customize your cloud/storm effect
// Frequency in days based on the day of the month - number 2 means every 2 days, for example (day 2,4,6 etc)
// For testing purposes, you can use 1 and cause the cloud to occur everyday
#define Clouds_Every_X_Days 1
// Percentage chance of a cloud happening today
// For testing purposes, you can use 100 and cause the cloud to have 100% chance of happening
#define Cloud_Chance_per_Day 50
// Minimum number of minutes for cloud duration. Don't use max duration of less than 6
#define Min_Cloud_Duration 7
// Maximum number of minutes for the cloud duration. Don't use max duration of more than 255
#define Max_Cloud_Duration 15
// Minimum number of clouds that can happen per day
#define Min_Clouds_per_Day 1
// Maximum number of clouds that can happen per day
#define Max_Clouds_per_Day 5
// Only start the cloud effect after this setting
// In this example, start could after 11:30am
#define Start_Cloud_After NumMins(12,00)
// Always end the cloud effect before this setting
// In this example, end could before 8:00pm
#define End_Cloud_Before NumMins(19,00)
// Percentage chance of a lightning happen for every cloud
// For testing purposes, you can use 100 and cause the lightning to have 100% chance of happening
#define Lightning_Chance_per_Cloud 10
// Note: Make sure to choose correct values that will work within your PWMSLope settings.
// For example, in our case, we could have a max of 5 clouds per day and they could last for 50 minutes.
// Which could mean 250 minutes of clouds. We need to make sure the PWMSlope can accomodate 250 minutes of effects or unforseen results could happen.
// Also, make sure that you can fit double those minutes between Start_Cloud_After and End_Cloud_Before.
// In our example, we have 510 minutes between Start_Cloud_After and End_Cloud_Before, so double the 250 minutes (or 500 minutes) can fit in that 510
minutes window.
// It's a tight fit, but it did.
//#define printdebug // Uncomment this for debug print on Serial Monitor window
//#define forcecloudcalculation // Uncomment this to force the cloud calculation to happen in the boot process.
// Change the values above to customize your cloud/storm effect
// ------------------------------------------------------------
// Do not change anything below here
static byte cloudchance=255;
static byte cloudduration=0;
static int cloudstart=0;
static byte numclouds=0;
static byte lightningchance=0;
static byte cloudindex=0;
static byte lightningstatus=0;
static int LastNumMins=0;
// Every day at midnight, we check for chance of cloud happening today
if (hour()==0 && minute()==0 && second()==0) cloudchance=255;
#ifdef forcecloudcalculation
if (cloudchance==255)
#else
if (hour()==0 && minute()==0 && second()==1 && cloudchance==255)
#endif
{
//Pick a random number between 0 and 99
cloudchance=random(100);
// if picked number is greater than Cloud_Chance_per_Day, we will not have clouds today
if (cloudchance>Cloud_Chance_per_Day) cloudchance=0;
// Check if today is day for clouds.
if ((day()%Clouds_Every_X_Days)!=0) cloudchance=0;
// If we have cloud today
if (cloudchance)
{
// pick a random number for number of clouds between Min_Clouds_per_Day and Max_Clouds_per_Day
numclouds=random(Min_Clouds_per_Day,Max_Clouds_per_Day);
// pick the time that the first cloud will start
// the range is calculated between Start_Cloud_After and the even distribuition of clouds on this day.
cloudstart=random(Start_Cloud_After,Start_Cloud_After+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
// pick a random number for the cloud duration of first cloud.
cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//Pick a random number between 0 and 99
lightningchance=random(100);
// if picked number is greater than Lightning_Chance_per_Cloud, we will not have lightning today
if (lightningchance>Lightning_Chance_per_Cloud) lightningchance=0;
}
}
// Now that we have all the parameters for the cloud, let's create the effect
if (ForceCloud)
{
ForceCloud=false;
cloudchance=1;
cloudduration=10;
lightningchance=1;
cloudstart=NumMins(hour(),minute())+1;
}
if (cloudchance)
{
//is it time for cloud yet?
if (NumMins(hour(),minute())>=cloudstart && NumMins(hour(),minute())<(cloudstart+cloudduration))
{
WhiteValue=ReversePWMSlope(cloudstart,cloudstart+cloudduration,WhiteValue,0,180);
if (lightningchance && (NumMins(hour(),minute())==(cloudstart+(cloudduration/2))) && second()<5)
{
if (random(100)<20) lightningstatus=1;
else lightningstatus=0;
if (lightningstatus)
{
WhiteValue=100;
BlueValue=100;
RoyalBlueValue=100;
}
else
{
WhiteValue=0;
BlueValue=0;
RoyalBlueValue=0;
}
delay(1);
}
}
if (NumMins(hour(),minute())>(cloudstart+cloudduration))
{
cloudindex++;
if (cloudindex < numclouds)
{
cloudstart=random(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_After)/(numclouds*2))*cloudindex*2),(Start_Cloud_After+(((End_Cloud_Before-Start_Cloud_Af
ter)/(numclouds*2))*cloudindex*2))+((End_Cloud_Before-Start_Cloud_After)/(numclouds*2)));
// pick a random number for the cloud duration of first cloud.
cloudduration=random(Min_Cloud_Duration,Max_Cloud_Duration);
//Pick a random number between 0 and 99
lightningchance=random(100);
// if picked number is greater than Lightning_Chance_per_Cloud, we will not have lightning today
if (lightningchance>Lightning_Chance_per_Cloud) lightningchance=0;
}
}
}
if (LastNumMins!=NumMins(hour(),minute()))
{
LastNumMins=NumMins(hour(),minute());
//ReefAngel.LCD.Clear(255,0,120,132,132);
//ReefAngel.LCD.DrawText(0,255,5,120,"C");
//ReefAngel.LCD.DrawText(0,255,11,120,"00:00");
//ReefAngel.LCD.DrawText(0,255,45,120,"L");
//ReefAngel.LCD.DrawText(0,255,51,120,"00:00");
if (cloudchance && (NumMins(hour(),minute())<cloudstart))
{
int x=0;
if ((cloudstart/60)>=10) x=11;
else x=17;
//ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart/60));
if ((cloudstart%60)>=10) x=29;
else x=35;
//ReefAngel.LCD.DrawText(0,255,x,120,(cloudstart%60));
}
ReefAngel.LCD.DrawText(0,255,90,120,cloudduration);
if (lightningchance)
{
int x=0;
if (((cloudstart+(cloudduration/2))/60)>=10) x=51;
else x=57;
//ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))/60));
if (((cloudstart+(cloudduration/2))%60)>=10) x=69;
else x=75;
//ReefAngel.LCD.DrawText(0,255,x,120,((cloudstart+(cloudduration/2))%60));
}
}
}
byte ReversePWMSlope(long cstart,long cend,byte PWMStart,byte PWMEnd, byte clength)
{
long n=elapsedSecsToday(now());
cstart*=60;
cend*=60;
if (n<cstart) return PWMStart;
if (n>=cstart && n<=(cstart+clength)) return map(n,cstart,cstart+clength,PWMStart,PWMEnd);
if (n>(cstart+clength) && n<(cend-clength)) return PWMEnd;
if (n>=(cend-clength) && n<=cend) return map(n,cend-clength,cend,PWMEnd,PWMStart);
if (n>cend) return PWMStart;
}
//End Cloud and Thunderstorm Function
//Begin One Time Setup functions
void setup()
{
ReefAngel.Init(); //Initialize controller
ReefAngel.InitMenu(pgm_read_word(&(menu_items[0])),SIZE(menu_items));
ReefAngel.AI.SetPort(daylightPWMPin); //Define where the AI Nano is plugged in
// Ports that are always on
ReefAngel.Relay.On(Port2); //Port 2 is the circulation pump
ReefAngel.Relay.On(Port4); //Port 4 is the skimmer
}
//End One Time Setup Functions
//Begin Main Program Loop
void loop()
{
// Define Which Ports Use Which Standard Functions
ReefAngel.StandardHeater(Port1); //This is the Main Display Heater
ReefAngel.DosingPump1(Port8); //Dosing Pump 1 is the Alkalinity Pump
ReefAngel.DosingPump2(Port7); //Dosing Pump 2 is the Calcium Pump
ReefAngel.ShowInterface(); //Draw the Standard Interface
//Audible Alarm Control Scheme
if (ReefAngel.Params.Temp1>810) //If the water temperature exceeds 81F
{
ReefAngel.PWM.SetActinic(100); //Sound the alarm
}
else
{
if (ReefAngel.Params.Temp1<770) //If the water temperature is less than 77F
{
ReefAngel.PWM.SetActinic(100); //Sound the alarm
}
else
{
ReefAngel.PWM.SetActinic(0); //Silence the alarm
}
}
//End of Audible Alarm Control Scheme
//AquaIllumination Nano Control Scheme
if (hour()>=22 || hour()<9)
{
WhiteValue=0;
BlueValue=MoonPhase()*0.06;
RoyalBlueValue=MoonPhase()*0.06;
ReefAngel.AI.SetChannel(White,WhiteValue);
ReefAngel.AI.SetChannel(Blue,BlueValue);
ReefAngel.AI.SetChannel(RoyalBlue,RoyalBlueValue);
}
else
{
WhiteValue=PWMSlope(9,0,22,0,0,100,180,0);
BlueValue=PWMSlope(9,0,22,0,6,100,120,MoonPhase()*0.06);
RoyalBlueValue=PWMSlope(9,0,22,0,6,100,120,MoonPhase()*0.06);
CheckCloud();
ReefAngel.AI.SetChannel(White,WhiteValue);
ReefAngel.AI.SetChannel(Blue,BlueValue);
ReefAngel.AI.SetChannel(RoyalBlue,RoyalBlueValue);
}
//End of AquaIllumination Nano Control Scheme
}
//End of Main Program Loop
//Begin of Section to Calculate Moon Phase
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;
}
}
//End of Section to Calculate Moon Phase