From 71b473c28b2b46722149e83ca15ebefbf4cbc843 Mon Sep 17 00:00:00 2001 From: GuruSR <17853390+GuruSR@users.noreply.github.com> Date: Tue, 12 Apr 2022 15:43:34 -0400 Subject: [PATCH] Version 1.4.3F. --- src/Watchy_GSR.cpp | 234 +++++++++++++++++++++++++-------------------- src/Watchy_GSR.h | 42 ++++---- src/Web-HTML.h | 31 +++--- 3 files changed, 168 insertions(+), 139 deletions(-) diff --git a/src/Watchy_GSR.cpp b/src/Watchy_GSR.cpp index ce75552..06b27c5 100644 --- a/src/Watchy_GSR.cpp +++ b/src/Watchy_GSR.cpp @@ -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,79 +594,14 @@ void WatchyGSR::init(String datetime){ }); RefreshCPU(CPUMAX); ArduinoOTA.begin(); - }else if (Menu.Item == MENU_OTAM){ - /*return index page which is stored in basicIndex */ - server.on("/", HTTP_GET, [=]() { - server.sendHeader("Connection", "close"); - server.send(200, "text/html", basicIndex); - OTATimer=millis(); - }); - server.on("/settings", HTTP_GET, [=]() { - String S = settingsA + GetSettings() + settingsB; - server.sendHeader("Connection", "close"); - server.send(200, "text/html", S); - OTATimer=millis(); - }); - server.on("/wifi", HTTP_GET, [=]() { - server.sendHeader("Connection", "close"); - server.send(200, "text/html", buildWiFiAPPage()); - OTATimer=millis(); - }); - server.on("/update", HTTP_GET, [=]() { - server.sendHeader("Connection", "close"); - server.send(200, "text/html", updateIndex); - OTATimer=millis(); - }); - server.on("/settings", HTTP_POST, [=](){ - if (server.argName(0) == "settings") { StoreSettings(server.arg(0)); RecordSettings(); } - server.sendHeader("Connection", "close"); - server.send(200, "text/html", settingsDone); - OTATimer=millis(); - }); - server.on("/wifi", HTTP_POST, [=](){ - uint8_t I = 0; - while (I < server.args()){ - parseWiFiPageArg(server.argName(I),server.arg(I)); I++; - } - server.sendHeader("Connection", "close"); - server.send(200, "text/html", wifiDone); - RecordSettings(); - OTAFail = millis() - 598000; - }); - server.on("/update", HTTP_POST, [](){ - server.sendHeader("Connection", "close"); - server.send(200, "text/plain", (Update.hasError()) ? "Upload Failed." : "Watchy will reboot!"); - delay(2000); - ESP.restart(); - }, []() { - HTTPUpload& upload = server.upload(); - if (upload.status == UPLOAD_FILE_START) { - OTATimer=millis(); - - if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size - OTAEnd=true; - } - } else if (upload.status == UPLOAD_FILE_WRITE) { - /* flashing firmware to ESP*/ - - if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) OTAEnd=true; - - } else if (upload.status == UPLOAD_FILE_END) { - if (Update.end(true)) { //true to set the size to the current progress - OTAEnd=true; - } - } - }); - RefreshCPU(CPUMAX); - server.begin(); - } + }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(); + 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. } @@ -660,7 +611,7 @@ void WatchyGSR::init(String datetime){ if (OTAEnd){ if (Menu.Item == MENU_OTAU) ArduinoOTA.end(); else if (Menu.Item == MENU_OTAM) server.stop(); - if (WatchyAPOn) wifiManager.stopConfigPortal(); + if (WatchyAPOn) server.stop(); VibeTo(false); OTAEnd=false; OTAUpdate=false; @@ -710,6 +661,74 @@ void WatchyGSR::init(String datetime){ deepSleep(); } +void WatchyGSR::StartWeb(){ + /*return index page which is stored in basicIndex */ + server.on("/", HTTP_GET, [=]() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", basicIndex); + OTATimer=millis(); + }); + server.on("/settings", HTTP_GET, [=]() { + String S = settingsA + GetSettings() + settingsB; + server.sendHeader("Connection", "close"); + server.send(200, "text/html", S); + OTATimer=millis(); + }); + server.on("/wifi", HTTP_GET, [=]() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", buildWiFiAPPage()); + OTATimer=millis(); + }); + server.on("/update", HTTP_GET, [=]() { + server.sendHeader("Connection", "close"); + server.send(200, "text/html", updateIndex); + OTATimer=millis(); + }); + server.on("/settings", HTTP_POST, [=](){ + if (server.argName(0) == "settings") { StoreSettings(server.arg(0)); RecordSettings(); } + server.sendHeader("Connection", "close"); + server.send(200, "text/html", settingsDone); + OTATimer=millis(); + }); + server.on("/wifi", HTTP_POST, [=](){ + uint8_t I = 0; + while (I < server.args()){ + parseWiFiPageArg(server.argName(I),server.arg(I)); I++; + } + server.sendHeader("Connection", "close"); + server.send(200, "text/html", wifiDone); + RecordSettings(); + OTAFail = millis() - 598000; + }); + server.on("/update", HTTP_POST, [](){ + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", (Update.hasError()) ? "Upload Failed." : "Watchy will reboot!"); + delay(2000); + ESP.restart(); + }, []() { + HTTPUpload& upload = server.upload(); + if (upload.status == UPLOAD_FILE_START) { + OTATimer=millis(); + + if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size + OTAEnd=true; + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + /* flashing firmware to ESP*/ + + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) OTAEnd=true; + + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + OTAEnd=true; + } + } + }); + RefreshCPU(CPUMAX); + server.begin(); +} + + void WatchyGSR::showWatchFace(){ bool B = (Battery.Last > Battery.MinLevel); if (Options.Performance > 0 && B) RefreshCPU((Options.Performance == 1 ? CPUMID : CPUMAX)); @@ -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; diff --git a/src/Watchy_GSR.h b/src/Watchy_GSR.h index 085fc0d..5017eb7 100644 --- a/src/Watchy_GSR.h +++ b/src/Watchy_GSR.h @@ -39,17 +39,19 @@ class WatchyGSR{ static SmallRTC SRTC; static SmallNTP SNTP; static GxEPD2_BW 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(); diff --git a/src/Web-HTML.h b/src/Web-HTML.h index 49800d3..d2045a5 100644 --- a/src/Web-HTML.h +++ b/src/Web-HTML.h @@ -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,