Version 1.4.3F.

This commit is contained in:
GuruSR 2022-04-12 15:43:34 -04:00 committed by GitHub
parent f1f7a64c36
commit 71b473c28b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 168 additions and 139 deletions

View File

@ -16,6 +16,16 @@ const float Reduce[5] = {1.0,0.8,0.6,0.4,0.2};
#define GSettings "GSR-Options"
#define GTZ "GSR-TZ"
/* Private
RTC_DATA_ATTR WatchyGSR::GSRWireless GSRWiFi;
RTC_DATA_ATTR WatchyGSR::CPUWork CPUSet;
RTC_DATA_ATTR WatchyGSR::Stepping Steps;
RTC_DATA_ATTR WatchyGSR::Optional Options;
RTC_DATA_ATTR WatchyGSR::DesignStyles WatchStyles;
RTC_DATA_ATTR WatchyGSR::MenuUse Menu;
*/
// Protected
RTC_DATA_ATTR struct GSRWireless final {
bool Requested; // Request WiFi.
bool Working; // Working on getting WiFi.
@ -32,19 +42,16 @@ RTC_DATA_ATTR struct GSRWireless final {
wifi_power_t TransmitPower;
wifi_event_id_t WiFiEventID;
} GSRWiFi;
RTC_DATA_ATTR struct CPUWork final {
uint32_t Freq;
bool Locked;
} CPUSet;
RTC_DATA_ATTR struct Stepping final {
uint8_t Hour;
uint8_t Minutes;
bool Reset;
uint32_t Yesterday;
} Steps;
RTC_DATA_ATTR struct Optional final {
bool TwentyFour; // If the face shows 24 hour or Am/Pm.
bool LightMode; // Light/Dark mode.
@ -66,6 +73,17 @@ RTC_DATA_ATTR struct Optional final {
bool BedTimeOrientation; // Make Buttons only work while Watch is in normal orientation.
uint8_t WatchFaceStyle; // Using the Style values from Defines_GSR.
} Options;
RTC_DATA_ATTR struct DesignStyles final {
uint8_t Count;
char Style[32 * MaxStyles];
} WatchStyles;
RTC_DATA_ATTR struct MenuUse final {
int8_t Style; // MENU_INNORMAL or MENU_INOPTIONS
int8_t Item; // What Menu Item is being viewed.
int8_t SubItem; // Used for menus that have sub items, like alarms and Sync Time.
int8_t SubSubItem; // Used mostly in the alarm to offset choice.
} Menu;
RTC_DATA_ATTR struct Designing final {
struct MenuPOS {
@ -116,21 +134,6 @@ RTC_DATA_ATTR struct Designing final {
byte BATTy; // 178
} Status;
} Design;
RTC_DATA_ATTR struct DesignStyles final {
uint8_t Count;
char Style[32 * MaxStyles];
} WatchStyles;
RTC_DATA_ATTR int GuiMode;
RTC_DATA_ATTR bool VibeMode; // Vibe Motor is On=True/Off=False, used for the Haptic and Alarms.
RTC_DATA_ATTR String WatchyStatus; // Used for the indicator in the bottom left, so when it changes, it asks for a screen refresh, if not, it doesn't.
RTC_DATA_ATTR int BasicWatchStyles;
RTC_DATA_ATTR bool DefaultWatchStyles; // States that the original 2 Watch Styles are to be added.
RTC_DATA_ATTR uint8_t UP_PIN; // Used to catch the different pin allocation for the up button.
RTC_DATA_ATTR uint64_t UP_MASK;
RTC_DATA_ATTR uint64_t BTN_MASK;
RTC_DATA_ATTR struct TimeData final {
time_t UTC_RAW; // Copy of the UTC on init.
tmElements_t UTC; // Copy of UTC only split up for usage.
@ -146,6 +149,16 @@ RTC_DATA_ATTR struct TimeData final {
bool BedTime; // If the hour is within the Bed Time settings.
} WatchTime;
RTC_DATA_ATTR int GuiMode;
RTC_DATA_ATTR bool VibeMode; // Vibe Motor is On=True/Off=False, used for the Haptic and Alarms.
RTC_DATA_ATTR String WatchyStatus; // Used for the indicator in the bottom left, so when it changes, it asks for a screen refresh, if not, it doesn't.
RTC_DATA_ATTR int BasicWatchStyles;
RTC_DATA_ATTR bool DefaultWatchStyles; // States that the original 2 Watch Styles are to be added.
RTC_DATA_ATTR uint8_t UP_PIN; // Used to catch the different pin allocation for the up button.
RTC_DATA_ATTR uint64_t UP_MASK;
RTC_DATA_ATTR uint64_t BTN_MASK;
RTC_DATA_ATTR float HWVer;
RTC_DATA_ATTR struct Countdown final {
bool Active;
uint8_t Hours;
@ -176,13 +189,6 @@ RTC_DATA_ATTR struct BatteryUse final {
float LowLevel; // The battery is about to get too low for the RTC to function.
} Battery;
RTC_DATA_ATTR struct MenuUse final {
int8_t Style; // MENU_INNORMAL or MENU_INOPTIONS
int8_t Item; // What Menu Item is being viewed.
int8_t SubItem; // Used for menus that have sub items, like alarms and Sync Time.
int8_t SubSubItem; // Used mostly in the alarm to offset choice.
} Menu;
RTC_DATA_ATTR struct NTPUse final {
uint8_t State; // State = 0=Off, 1=Start WiFi, 2=Wait for WiFi, TZ, send NTP request, etc, Finish. See function ProcessNTP();
uint8_t Wait; // Counts up to 3 minutes, then fails.
@ -323,13 +329,14 @@ void WatchyGSR::init(String datetime){
if (Darkness.Went && UpRight()){
if (Button == 5 && Options.SleepStyle > 1 && Options.SleepStyle != 4){ // Accelerometer caused this.
if (Options.SleepMode == 0) Options.SleepMode = 2; // Do this to avoid someone accidentally not setting this before usage.
UpdateClock(); // Make sure these are done during times when it won't.
Darkness.Woke=true; Updates.Tapped=true; Darkness.Last=millis(); Darkness.Tilt = Darkness.Last; UpdateDisp = true; // Update Screen to new state.
}else if (Button == 6 && !WatchTime.BedTime){ // Wrist.
UpdateClock(); // Make sure these are done during times when it won't.
Darkness.Woke=true; Darkness.Last=millis(); Darkness.Tilt = Darkness.Last; UpdateDisp = true; // Do this anyways, always.
}else if (Button != 0) {
Darkness.Woke=true; Darkness.Last=millis(); Darkness.Tilt = Darkness.Last; UpdateDisp = true; // Update Screen to new state.
}
}
if (Darkness.Woke || Button != 0) UpdateClock(); // Make sure this is done when buttons are pressed for a wakeup.
SRTC.resetWake();
break;
default: //reset
@ -340,15 +347,12 @@ void WatchyGSR::init(String datetime){
Battery.LowLevel = SRTC.getRTCBattery(true);
UP_PIN = 32;
UP_MASK = GPIO_SEL_32;
if (SRTC.getType() == PCF8563){
UP_PIN = 35; UP_MASK = GPIO_SEL_35;
if (SRTC.getADCPin() == 35) { UP_PIN = 32; UP_MASK = GPIO_SEL_32; }
}
HWVer = 1.0;
if (SRTC.getType() == PCF8563){ if (SRTC.getADCPin() == 35) { HWVer =1.5; UP_PIN = 32; UP_MASK = GPIO_SEL_32; } else { HWVer = 2.0; UP_PIN = 35; UP_MASK = GPIO_SEL_35; } }
BTN_MASK = MENU_MASK|BACK_MASK|UP_MASK|DOWN_MASK;
initZeros();
setupDefaults();
Rebooted=true;
Darkness.Woke=true;
_bmaConfig();
if (DefaultWatchStyles){
I = AddWatchStyle("Classic GSR");
@ -378,11 +382,21 @@ void WatchyGSR::init(String datetime){
WaitForNext=false;
Updates.Full=true;
UpdateDisp=true;
Darkness.Went=true; // Fake this.
Darkness.Woke=true;
Darkness.Last=millis();
Darkness.Tilt=Darkness.Last;
break;
}
B = true;
if (Darkness.Went)
{
if (Options.SleepStyle == 4) B = (Updates.Tapped || Battery.DarkState != Battery.State);
else B = (Darkness.Woke || Button != 0 || Battery.DarkState != Battery.State);
}
if (((Battery.Last > Battery.LowLevel || Button != 0 || Updates.Tapped || Darkness.Woke) && !(Options.SleepStyle == 4 && Darkness.Went && !Updates.Tapped)) || (Battery.DarkState != Battery.State)){
if (B || Updates.Full || WatchTime.NewMinute){
//Init interrupts.
attachInterrupt(digitalPinToInterrupt(MENU_PIN), std::bind(&WatchyGSR::handleInterrupt,this), HIGH);
attachInterrupt(digitalPinToInterrupt(BACK_PIN), std::bind(&WatchyGSR::handleInterrupt,this), HIGH);
@ -520,7 +534,7 @@ void WatchyGSR::init(String datetime){
OTAEnd |= (!WiFi.softAP(WiFi_AP_SSID, WiFi_AP_PSWD, 1, WiFi_AP_HIDE, WiFi_AP_MAXC));
if (!OTAEnd) UpdateWiFiPower();
}else if (WiFi.getMode() == WIFI_AP){
wifiManager.startWebPortal();
WatchyGSR::StartWeb();
Menu.SubItem++;
setStatus("WiFi-AP");
UpdateDisp=Showing();
@ -533,10 +547,12 @@ void WatchyGSR::init(String datetime){
Menu.SubItem = 0;
break;
}
if (wifiManager.process()){ // Setting worked.
//if (wifiManager.process()){ // Setting worked.
server.handleClient();
/*if (Server.handleRequests()){
Menu.SubItem = 0;
break;
}
}*/
if (millis() - APLoop > 8000){
Menu.SubItem = roller(Menu.SubItem + 1, 2,4);
UpdateDisp = Showing();
@ -578,7 +594,74 @@ void WatchyGSR::init(String datetime){
});
RefreshCPU(CPUMAX);
ArduinoOTA.begin();
}else if (Menu.Item == MENU_OTAM){
}else if (Menu.Item == MENU_OTAM) WatchyGSR::StartWeb();
Menu.SubItem++;
showWatchFace();
break;
case 3: // Monitor back button and turn WiFi off if it happens, or if duration is longer than 2 minutes.
if (WiFi.status() == WL_DISCONNECTED) OTAEnd = true;
else if (Menu.Item == MENU_OTAU) ArduinoOTA.handle();
else if (Menu.Item == MENU_OTAM) server.handleClient();
if (getButtonPins() != 2) OTATimer = millis(); // Not pressing "BACK".
if (millis() - OTATimer > 10000 || millis() - OTAFail > 600000) OTAEnd = true; // Fail if holding back for 10 seconds OR 600 seconds has passed.
}
}
// OTAEnd code.
if (OTAEnd){
if (Menu.Item == MENU_OTAU) ArduinoOTA.end();
else if (Menu.Item == MENU_OTAM) server.stop();
if (WatchyAPOn) server.stop();
VibeTo(false);
OTAEnd=false;
OTAUpdate=false;
WatchyAPOn = false;
endWiFi();
if (Menu.Item != MENU_TOFF){
Menu.SubItem=0;
UpdateUTC();
UpdateClock();
}
Battery.UpCount=0; // Stop it from thinking the battery went wild.
UpdateDisp=Showing();
}
// Don't do anything time sensitive while in OTA Update.
if (!Sensitive){
// Here, check for button presses and respond, done here to avoid turbo button presses.
if (!DarkWait()) GoDark();
handleInterrupt();
if (Button > 0) { handleButtonPress(Button); Button = 0; }
if (UpdateDisp) showWatchFace(); //partial updates on tick
if (!Updates.Init) { if (!(InTurbo() || DarkWait())) DisplaySleep(); }
AlarmsOn =(Alarms_Times[0] > 0 || Alarms_Times[1] > 0 || Alarms_Times[2] > 0 || Alarms_Times[3] > 0 || TimerDown.ToneLeft > 0);
ActiveMode = (InTurbo() || DarkWait() || NTPData.State > 0 || AlarmsOn || WatchyAPOn || OTAUpdate || NTPData.TimeTest || WatchTime.DeadRTC || GSRWiFi.Requested);
if (WatchTime.DeadRTC && Options.NeedsSaving) RecordSettings();
RefreshCPU(CPUDEF);
Since=50-(millis()-Since);
if (Since <= 50) delay(Since);
}
WatchTime.NewMinute=false;
IDidIt=false;
Sensitive = ((OTAUpdate && Menu.SubItem == 3) || (NTPData.TimeTest && Menu.SubItem == 2));
}
}
if (Button > 0) { handleButtonPress(Button); Button = 0; }
processWiFiRequest(); // Process any WiFi requests.
if (UpdateDisp) showWatchFace(); //partial updates on tick
AlarmsOn =(Alarms_Times[0] > 0 || Alarms_Times[1] > 0 || Alarms_Times[2] > 0 || Alarms_Times[3] > 0 || TimerDown.ToneLeft > 0);
ActiveMode = (InTurbo() || DarkWait() || NTPData.State > 0 || AlarmsOn || WatchyAPOn || OTAUpdate || NTPData.TimeTest || WatchTime.DeadRTC);
if (ActiveMode) DoOnce = true;
}
}
deepSleep();
}
void WatchyGSR::StartWeb(){
/*return index page which is stored in basicIndex */
server.on("/", HTTP_GET, [=]() {
server.sendHeader("Connection", "close");
@ -644,71 +727,7 @@ void WatchyGSR::init(String datetime){
RefreshCPU(CPUMAX);
server.begin();
}
Menu.SubItem++;
showWatchFace();
break;
case 3: // Monitor back button and turn WiFi off if it happens, or if duration is longer than 2 minutes.
if (WiFi.status() == WL_DISCONNECTED) OTAEnd = true;
else if (Menu.Item == MENU_OTAU) ArduinoOTA.handle();
else if (Menu.Item == MENU_OTAM) server.handleClient();
if (getButtonPins() != 2) OTATimer = millis(); // Not pressing "BACK".
if (millis() - OTATimer > 10000 || millis() - OTAFail > 600000) OTAEnd = true; // Fail if holding back for 10 seconds OR 600 seconds has passed.
}
}
// OTAEnd code.
if (OTAEnd){
if (Menu.Item == MENU_OTAU) ArduinoOTA.end();
else if (Menu.Item == MENU_OTAM) server.stop();
if (WatchyAPOn) wifiManager.stopConfigPortal();
VibeTo(false);
OTAEnd=false;
OTAUpdate=false;
WatchyAPOn = false;
endWiFi();
if (Menu.Item != MENU_TOFF){
Menu.SubItem=0;
UpdateUTC();
UpdateClock();
}
Battery.UpCount=0; // Stop it from thinking the battery went wild.
UpdateDisp=Showing();
}
// Don't do anything time sensitive while in OTA Update.
if (!Sensitive){
// Here, check for button presses and respond, done here to avoid turbo button presses.
if (!DarkWait()) GoDark();
handleInterrupt();
if (Button > 0) { handleButtonPress(Button); Button = 0; }
if (UpdateDisp) showWatchFace(); //partial updates on tick
if (!Updates.Init) { if (!(InTurbo() || DarkWait())) DisplaySleep(); }
AlarmsOn =(Alarms_Times[0] > 0 || Alarms_Times[1] > 0 || Alarms_Times[2] > 0 || Alarms_Times[3] > 0 || TimerDown.ToneLeft > 0);
ActiveMode = (InTurbo() || DarkWait() || NTPData.State > 0 || AlarmsOn || WatchyAPOn || OTAUpdate || NTPData.TimeTest || WatchTime.DeadRTC || GSRWiFi.Requested);
if (WatchTime.DeadRTC && Options.NeedsSaving) RecordSettings();
RefreshCPU(CPUDEF);
Since=50-(millis()-Since);
if (Since <= 50) delay(Since);
}
WatchTime.NewMinute=false;
IDidIt=false;
Sensitive = ((OTAUpdate && Menu.SubItem == 3) || (NTPData.TimeTest && Menu.SubItem == 2));
}
}
if (Button > 0) { handleButtonPress(Button); Button = 0; }
processWiFiRequest(); // Process any WiFi requests.
if (UpdateDisp) showWatchFace(); //partial updates on tick
AlarmsOn =(Alarms_Times[0] > 0 || Alarms_Times[1] > 0 || Alarms_Times[2] > 0 || Alarms_Times[3] > 0 || TimerDown.ToneLeft > 0);
ActiveMode = (InTurbo() || DarkWait() || NTPData.State > 0 || AlarmsOn || WatchyAPOn || OTAUpdate || NTPData.TimeTest || WatchTime.DeadRTC);
if (ActiveMode) DoOnce = true;
}
}
deepSleep();
}
void WatchyGSR::showWatchFace(){
bool B = (Battery.Last > Battery.MinLevel);
@ -1139,6 +1158,9 @@ void WatchyGSR::drawMenu(){
case 1:
O = "Battery: " + String(Battery.Last - (Battery.Last > MaxBattery ? 1.00 : 0.00)) + "V";
break;
case 2:
if (HWVer > 1.0){ O = (HWVer == 2.0) ? "V2.0 PCF8563" : "V1.5 PCF8563"; } else O = "V1 DS3231M";
break;
}
}else if (Menu.Item == MENU_SAVE){ // Performance
if (Options.Performance == 2 || WatchTime.DeadRTC) O = "Battery Saving";
@ -1683,7 +1705,7 @@ void WatchyGSR::handleButtonPress(uint8_t Pressed){
UpdateDisp = true; // Quick Update.
SetTurbo();
}else if (Menu.Item == MENU_INFO){ // Information
Menu.SubItem = roller(Menu.SubItem + 1, 0, 1);
Menu.SubItem = roller(Menu.SubItem + 1, 0, 2);
DoHaptic = true;
UpdateDisp = true; // Quick Update.
SetTurbo();
@ -3066,7 +3088,7 @@ void WatchyGSR::StoreSettings(String FromUser){
uint16_t V;
size_t L;
bool Ok;
uint8_t I, A, W, NewV; // For WiFi storage.
uint8_t I, A, W, NewV; // For WiFi
String S;
J = FromUser.length(); if (J < 5) return;

View File

@ -39,17 +39,19 @@ class WatchyGSR{
static SmallRTC SRTC;
static SmallNTP SNTP;
static GxEPD2_BW<GxEPD2_154_D67, GxEPD2_154_D67::HEIGHT> display;
static constexpr const char* Build = "1.4.3E";
static constexpr const char* Build = "1.4.3F";
enum DesOps {dSTATIC, dLEFT, dRIGHT, dCENTER};
public:
WatchyGSR();
virtual void init(String datetime = "") final;
void showWatchFace();
void drawWatchFace(); //override this method for different watch faces
void drawTime();
void drawDay();
void drawDate();
void drawYear();
virtual void StartWeb() final;
virtual void showWatchFace();
virtual void drawWatchFace(); //override this method for different watch faces
virtual void drawTime();
virtual void drawDay();
virtual void drawDate();
virtual void drawYear();
virtual void handleButtonPress(uint8_t Pressed) final;
virtual void deepSleep() final;
virtual float getBatteryVoltage() final;
@ -64,26 +66,26 @@ class WatchyGSR{
virtual String MakeMinutes(uint8_t Minutes) final;
virtual uint16_t ForeColor() final;
virtual uint16_t BackColor() final;
void InsertPost();
bool OverrideBitmap();
bool OverrideSleepBitmap();
void InsertDefaults();
void InsertOnMinute();
void InsertWiFi();
void InsertWiFiEnding();
void InsertAddWatchStyles();
void InsertDrawWatchStyle(uint8_t StyleID);
void InsertInitWatchStyle(uint8_t StyleID);
virtual void InsertPost();
virtual bool OverrideBitmap();
virtual bool OverrideSleepBitmap();
virtual void InsertDefaults();
virtual void InsertOnMinute();
virtual void InsertWiFi();
virtual void InsertWiFiEnding();
virtual void InsertAddWatchStyles();
virtual void InsertDrawWatchStyle(uint8_t StyleID);
virtual void InsertInitWatchStyle(uint8_t StyleID);
virtual uint8_t AddWatchStyle(String StyleName) final;
String InsertNTPServer();
virtual String InsertNTPServer();
virtual void AllowDefaultWatchStyles(bool Allow = true) final;
virtual void AskForWiFi() final;
virtual wl_status_t currentWiFi() final;
virtual void endWiFi() final;
virtual void getAngle(uint16_t Angle, uint8_t Away, uint8_t &X, uint8_t &Y) final;
virtual bool SafeToDraw() final;
void initWatchFaceStyle();
void drawWatchFaceStyle();
virtual void initWatchFaceStyle();
virtual void drawWatchFaceStyle();
private:
void setStatus(String Status);
void drawMenu();

View File

@ -33,19 +33,24 @@ static const char basicIndex[] = {
0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x20, 0x4F, 0x70, 0x74, 0x69, 0x6F, 0x6E, 0x73, 0x3C, 0x2F,
0x75, 0x3E, 0x3C, 0x2F, 0x62, 0x3E, 0x3C, 0x2F, 0x66, 0x6F, 0x6E, 0x74, 0x3E, 0x3C, 0x2F, 0x63,
0x65, 0x6E, 0x74, 0x65, 0x72, 0x3E, 0x3C, 0x62, 0x72, 0x3E, 0x3C, 0x2F, 0x74, 0x64, 0x3E, 0x3C,
0x2F, 0x74, 0x72, 0x3E, 0x3C, 0x74, 0x64, 0x3E, 0x3C, 0x66, 0x6F, 0x6E, 0x74, 0x20, 0x73, 0x69,
0x7A, 0x65, 0x3D, 0x32, 0x3E, 0x3C, 0x66, 0x6F, 0x72, 0x6D, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6F,
0x6E, 0x3D, 0x27, 0x2F, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6E, 0x67, 0x73, 0x27, 0x3E, 0x3C, 0x62,
0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x27, 0x73, 0x75, 0x62, 0x6D,
0x69, 0x74, 0x27, 0x3E, 0x42, 0x61, 0x63, 0x6B, 0x75, 0x70, 0x20, 0x26, 0x20, 0x52, 0x65, 0x73,
0x74, 0x6F, 0x72, 0x65, 0x20, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6E, 0x67, 0x73, 0x3C, 0x2F, 0x62,
0x75, 0x74, 0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, 0x66, 0x6F, 0x72, 0x6D, 0x3E, 0x3C, 0x66, 0x6F,
0x72, 0x6D, 0x20, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x3D, 0x27, 0x2F, 0x77, 0x69, 0x66,
0x69, 0x27, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D,
0x27, 0x73, 0x75, 0x62, 0x6D, 0x69, 0x74, 0x27, 0x3E, 0x45, 0x64, 0x69, 0x74, 0x20, 0x41, 0x64,
0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x61, 0x6C, 0x20, 0x57, 0x69, 0x46, 0x69, 0x20, 0x41, 0x63,
0x63, 0x65, 0x73, 0x73, 0x20, 0x50, 0x6F, 0x69, 0x6E, 0x74, 0x73, 0x3C, 0x2F, 0x62, 0x75, 0x74,
0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, 0x66, 0x6F, 0x72, 0x6D, 0x3E, 0x3C, 0x66, 0x6F, 0x72, 0x6D,
0x2F, 0x74, 0x72, 0x3E, 0x3C, 0x74, 0x72, 0x3E, 0x3C, 0x74, 0x64, 0x3E, 0x3C, 0x66, 0x6F, 0x6E,
0x74, 0x20, 0x73, 0x69, 0x7A, 0x65, 0x3D, 0x32, 0x3E, 0x3C, 0x66, 0x6F, 0x72, 0x6D, 0x20, 0x61,
0x63, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x27, 0x2F, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6E, 0x67, 0x73,
0x27, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x27,
0x73, 0x75, 0x62, 0x6D, 0x69, 0x74, 0x27, 0x3E, 0x42, 0x61, 0x63, 0x6B, 0x75, 0x70, 0x20, 0x26,
0x20, 0x52, 0x65, 0x73, 0x74, 0x6F, 0x72, 0x65, 0x20, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6E, 0x67,
0x73, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F, 0x66, 0x6F, 0x72, 0x6D,
0x3E, 0x3C, 0x2F, 0x66, 0x6F, 0x6E, 0x74, 0x3E, 0x3C, 0x2F, 0x74, 0x64, 0x3E, 0x3C, 0x2F, 0x74,
0x72, 0x3E, 0x3C, 0x74, 0x72, 0x3E, 0x3C, 0x74, 0x64, 0x3E, 0x3C, 0x66, 0x6F, 0x6E, 0x74, 0x20,
0x73, 0x69, 0x7A, 0x65, 0x3D, 0x32, 0x3E, 0x3C, 0x66, 0x6F, 0x72, 0x6D, 0x20, 0x61, 0x63, 0x74,
0x69, 0x6F, 0x6E, 0x20, 0x3D, 0x27, 0x2F, 0x77, 0x69, 0x66, 0x69, 0x27, 0x3E, 0x3C, 0x62, 0x75,
0x74, 0x74, 0x6F, 0x6E, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x27, 0x73, 0x75, 0x62, 0x6D, 0x69,
0x74, 0x27, 0x3E, 0x45, 0x64, 0x69, 0x74, 0x20, 0x41, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6F, 0x6E,
0x61, 0x6C, 0x20, 0x57, 0x69, 0x46, 0x69, 0x20, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x20, 0x50,
0x6F, 0x69, 0x6E, 0x74, 0x73, 0x3C, 0x2F, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x3E, 0x3C, 0x2F,
0x66, 0x6F, 0x72, 0x6D, 0x3E, 0x3C, 0x2F, 0x66, 0x6F, 0x6E, 0x74, 0x3E, 0x3C, 0x2F, 0x74, 0x64,
0x3E, 0x3C, 0x2F, 0x74, 0x72, 0x3E, 0x3C, 0x74, 0x72, 0x3E, 0x3C, 0x74, 0x64, 0x3E, 0x3C, 0x66,
0x6F, 0x6E, 0x74, 0x20, 0x73, 0x69, 0x7A, 0x65, 0x3D, 0x32, 0x3E, 0x3C, 0x66, 0x6F, 0x72, 0x6D,
0x20, 0x61, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x3D, 0x27, 0x2F, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65,
0x27, 0x3E, 0x3C, 0x62, 0x75, 0x74, 0x74, 0x6F, 0x6E, 0x20, 0x74, 0x79, 0x70, 0x65, 0x3D, 0x27,
0x73, 0x75, 0x62, 0x6D, 0x69, 0x74, 0x27, 0x3E, 0x55, 0x70, 0x6C, 0x6F, 0x61, 0x64, 0x20, 0x4E,