├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── ESP32RemoteForVictron ├── ESP32RemoteForVictron.ino ├── fonts │ ├── NotoSansBold15.h │ ├── NotoSansBold24.h │ ├── NotoSansBold36.h │ └── NotoSansBold72.h ├── general_settings.h ├── pins_config.h ├── rm67162.cpp ├── rm67162.h └── secret_settings.h ├── LICENSE ├── README.md └── images ├── image01.jpg ├── image02.jpg └── image03.jpg /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /ESP32RemoteForVictron/ESP32RemoteForVictron.ino: -------------------------------------------------------------------------------- 1 | // ESP32 Victron Monitor (version 1.9.7) 2 | // 3 | // Copyright Rob Latour, 2025 4 | // License: MIT 5 | // https://github.com/roblatour/ESP32RemoteForVictron 6 | // 7 | // version 1.9.7 - added option to show wattage coming into / going out of battery 8 | // version 1.9.6 - corrected problem with displaying total AC Load; ShouldTheDisplayBeOn tweaked to ensure SleepTime is correctly calculated 9 | // version 1.9.5 - adjusted calculation of solar watts and total grid watts when over 100kw to avoid displaying wrong results if updates not received when expected 10 | // version 1.9.4 - Made the use of millis roll over safe 11 | // version 1.9.3 - removed reference to timezone.h; refactoring of variable names; updated comments for SECRET_SETTINGS_MQTT_Broker 12 | // version 1.9.2 - after further review removed unneeded millis() roll over logic; 13 | // thanks to Nonstle for pointing this out at: https://github.com/roblatour/ESP32RemoteForVictron/issues/4 14 | // version 1.9.1 - corrected for millis() roll over 15 | // version 1.9 - corrected for screen refresh timings, deep sleep duration beyond 35 minutes, millis() roll over (which happens approximately every 49 days) 16 | // version 1.8 - updated code for better memory management, included a few additional comments 17 | // version 1.7 - updated to support both v1 and v2 of the Lily-go T-DISPLAY S3 AMOLED boards 18 | // version 1.6 - updated to take charging state from Multiplus if in ESS mode 19 | // version 1.5 - added an option to specify what is displayed under the battery percent charged; added an option to round numbers 20 | // version 1.4 - added deep sleep option to save power when the screen doesn't need to be on, added option to allow another system to send the periodic keep alive requests 21 | // version 1.3 - added options to show/hide charger and/or inverter status 22 | // version 1.2 - added data gathering and reporting for Grid 2 input, Grid 3 input, and AC Load 3 23 | // version 1.1 - integrated a timer for automatically turning the display on/off at specified times 24 | // version 1 - initial release 25 | // 26 | // Design and tested with a Victron Multiplus II 12v system, monitored by a Raspberry Pi Zero 2 W running Victron Venus Firmware v3.3 27 | // 28 | // Compile and upload using Arduino IDE (2.3.2 or greater) 29 | // 30 | // Physical board: LILYGO T-Display-S3 AMOLED 31 | // Arduino - Board Manager: esp32 by Espressif Systems : version 2.0.11 (ref: https://forum.arduino.cc/t/esp32-s3-sudden-compile-error/1177237/24) 32 | // Arduino Board selection: ESP32S3 Dev Module 33 | // 34 | // Arduino - File - Preferences - Additional Board Manager URLs: https://dl.espressif.com/dl/package_esp32_index.json 35 | // 36 | // Arduino Tools settings: 37 | // USB CDC On Boot: Enabled 38 | // CPU Frequency: 240MHz (WiFi) 39 | // Core Debug Level: None 40 | // USB DFU On Boot: Enabled (Requires USB-OTG Mode) 41 | // Erase All Flash Before Upload: Disabled 42 | // Events Run On: Core 1 43 | // Flash Mode: QIO 80Mhz 44 | // Flash Size: 16MB (128MB) 45 | // JTAG Adapter: Disabled 46 | // Arduino Runs On: Core 1 47 | // USB Firmware MSC On Boot: Disabled 48 | // Partition Scheme: 16 M Flash (3MB APP/9.9MB FATFS) 49 | // PSRAM: OPI PSRAM 50 | // Upload Mode: UART0 / Hardware CDC 51 | // Upload Speed: 921600 52 | // USB Mode: Hardware CDC and JTAG 53 | // 54 | // Programmer ESPTool 55 | 56 | // User settings 57 | #include "general_settings.h" 58 | #include "secret_settings.h" 59 | 60 | // Buttons 61 | #define topButtonIfUSBIsOnTheLeft 21 62 | #define bottomButtonIfUSBIsOnTheLeft 0 63 | 64 | // Globals 65 | const String programName = "ESP32 Remote for Victron"; 66 | const String programVersion = "(Version 1.9.7)"; 67 | const String programURL = "https://github.com/roblatour/ESP32RemoteForVictron"; 68 | 69 | RTC_DATA_ATTR bool initialStartupShowSplashScreen = true; 70 | 71 | RTC_DATA_ATTR bool initialStartupLoadVictronInstallationAndMultiplusIDs = true; 72 | RTC_DATA_ATTR char VictronInstallationIDArray[13]; 73 | RTC_DATA_ATTR char MultiplusThreeDigitIDArray[4]; 74 | RTC_DATA_ATTR char SolarChargerThreeDigitIDArray[4]; 75 | RTC_DATA_ATTR bool ESSIsBeingUsed = false; 76 | 77 | String VictronInstallationID; 78 | String MultiplusThreeDigitID; 79 | String SolarChargerThreeDigitID; 80 | 81 | const int dataPoints = 13; 82 | bool awaitingDataToBeReceived[dataPoints]; 83 | bool awaitingInitialTransmissionOfAllDataPoints; 84 | 85 | float gridInL1Watts = 0.0; 86 | float gridInL2Watts = 0.0; 87 | float gridInL3Watts = 0.0; 88 | 89 | float solarWatts = 0.0; 90 | 91 | float batterySOC = 0.0; 92 | float batteryTTG = 0.0; 93 | float batteryPower = 0.0; 94 | float batteryTemperature = 0.0; 95 | String chargingState = "Unknown"; 96 | 97 | float ACOutL1Watts = 0.0; 98 | float ACOutL2Watts = 0.0; 99 | float ACOutL3Watts = 0.0; 100 | 101 | enum multiplusMode 102 | { 103 | ChargerOnly, 104 | InverterOnly, 105 | On, 106 | Off, 107 | Unknown 108 | }; 109 | multiplusMode currentMultiplusMode = Unknown; 110 | 111 | enum multiplusFunction 112 | { 113 | Charger, 114 | Inverter 115 | }; 116 | 117 | int topButton, bottomButton; 118 | 119 | // Display 120 | #include // download and use the entire TFT_eSPI https://github.com/Xinyuan-LilyGO/LilyGo-AMOLED-Series/tree/master/libdeps 121 | #include "rm67162.h" // included in the github package for this sketch, but also available from https://github.com/Xinyuan-LilyGO/T-Display-S3-AMOLED/tree/main/examples/factory 122 | #include "fonts/NotoSansBold15.h" // included in the github package for this sketch, based on https://fonts.google.com/noto/specimen/Noto+Sans 123 | #include "fonts/NotoSansBold24.h" // " 124 | #include "fonts/NotoSansBold36.h" // " 125 | #include "fonts/NotoSansBold72.h" // " 126 | 127 | TFT_eSPI tft = TFT_eSPI(); 128 | TFT_eSprite sprite = TFT_eSprite(&tft); 129 | 130 | // MQTT 131 | #include // https://github.com/plapointe6/EspMQTTClient (v1.13.3) 132 | #include 133 | 134 | EspMQTTClient client( 135 | SECRET_SETTINGS_WIFI_SSID, 136 | SECRET_SETTINGS_WIFI_PASSWORD, 137 | SECRET_SETTINGS_MQTT_Broker, 138 | SECRET_SETTINGS_MQTT_UserID, 139 | SECRET_SETTINGS_MQTT_Password, 140 | SECRET_SETTINGS_MQTT_ClientName, 141 | SECRET_SETTINGS_MQTT_Port); 142 | 143 | unsigned long lastMQTTUpdateReceived = 0UL; 144 | 145 | // JSON 146 | #include // Ardiuno Library Manager, by Benoit Blanchon, https://arduinojson.org/?utm_source=meta&utm_medium=library.properties (v7.4.1) 147 | 148 | // Non blocking delay library 149 | #include "FireTimer.h" // Ardiuno Library Manager, by PowerBroker2, https://github.com/PowerBroker2/FireTimer (v1.0.5) 150 | FireTimer msTimer; 151 | 152 | // Time stuff 153 | #include 154 | #include // version 1.6.1 https://www.arduino.cc/reference/en/libraries/time/ 155 | #include 156 | #include 157 | 158 | const char *primaryNTPServer = GENERAL_SETTINGS_PRIMARY_TIME_SERVER; 159 | const char *secondaryNTPServer = GENERAL_SETTINGS_SECONDARY_TIME_SERVER; 160 | const char *tertiaryNTPSever = GENERAL_SETTINGS_TERTIARY_TIME_SERVER; 161 | const char *timeZone = GENERAL_SETTINGS_MY_TIME_ZONE; 162 | 163 | bool turnOnDisplayAtSpecificTimesOnly = GENERAL_SETTINGS_TURN_ON_DISPLAY_AT_SPECIFIC_TIMES_ONLY; 164 | unsigned long keepDisplayOnStartTime = 0UL; 165 | unsigned long keepDisplayOnTimeOut = 0UL; 166 | 167 | // report a timeout after this many seconds of no data being received 168 | const int timeOutInSeconds = 61; 169 | const int timeOutInMilliSeconds = timeOutInSeconds * 1000; 170 | 171 | bool theDisplayIsCurrentlyOn; 172 | int sleepHour, sleepMinute, wakeHour, wakeMinute; 173 | 174 | // Debug 175 | bool generalDebugOutput = false; 176 | bool verboseDebugOutput = false; 177 | 178 | void SetGreenLEDOff() 179 | { 180 | 181 | // With the AMOLED v1 board pin 38 should be set to LOW to turn off the green LED 182 | 183 | const int greenLEDPin = 38; 184 | pinMode(greenLEDPin, OUTPUT); 185 | digitalWrite(greenLEDPin, LOW); 186 | } 187 | 188 | void SetDisplayOn() 189 | { 190 | 191 | // On the AMOLED v2 board pin 38 must be set to HIGH for the display to work 192 | const int DisplayPin = 38; 193 | pinMode(DisplayPin, OUTPUT); 194 | digitalWrite(DisplayPin, HIGH); 195 | } 196 | 197 | void SetupTopAndBottomButtons() 198 | { 199 | 200 | // Notes: 201 | // This sketch was built for a LILYGO T-Display-S3 AMOLED and as such: 202 | // 1. The two buttons on the device have built in hardware debounce protection, so debounce logic is not needed in this sketch 203 | // 2. The postion of the 'top' and 'bottom' buttons are relative to the side on which the USB cable is plugged into, so the code below adjusts for that 204 | 205 | // enable buttons 206 | pinMode(topButtonIfUSBIsOnTheLeft, INPUT); 207 | pinMode(bottomButtonIfUSBIsOnTheLeft, INPUT); 208 | 209 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 210 | { 211 | topButton = bottomButtonIfUSBIsOnTheLeft; 212 | bottomButton = topButtonIfUSBIsOnTheLeft; 213 | } 214 | else 215 | { 216 | topButton = topButtonIfUSBIsOnTheLeft; 217 | bottomButton = bottomButtonIfUSBIsOnTheLeft; 218 | }; 219 | } 220 | 221 | void RefreshDisplay() 222 | { 223 | lcd_PushColors(0, 0, TFT_WIDTH, TFT_HEIGHT, (uint16_t *)sprite.getPointer()); 224 | } 225 | 226 | void ChangeMultiplusMode(multiplusFunction option) 227 | { 228 | 229 | // if no choice is made within this timeout period then return to the previous screen without making any changes 230 | int numberOfMinutesUserHasToMakeAChoiceBeforeTimeOut = 1; 231 | 232 | SetKeepDisplayOnTimeOut(numberOfMinutesUserHasToMakeAChoiceBeforeTimeOut); 233 | 234 | // show the opening prompt 235 | 236 | sprite.fillSprite(TFT_BLACK); 237 | 238 | // this should not happen, but throw an error if the current Multiplus mode is unknown 239 | 240 | if (currentMultiplusMode == Unknown) 241 | { 242 | sprite.setTextDatum(MC_DATUM); 243 | sprite.setTextColor(TFT_RED, TFT_BLACK); 244 | sprite.loadFont(NotoSansBold24); 245 | sprite.drawString("Multiplus mode cannot be changed", TFT_WIDTH / 2, TFT_HEIGHT / 2); 246 | RefreshDisplay(); 247 | sprite.unloadFont(); 248 | delay(5000); 249 | 250 | // keep the display on for one minute 251 | SetKeepDisplayOnTimeOut(1); 252 | 253 | return; 254 | }; 255 | 256 | // show opening prompt 257 | 258 | sprite.loadFont(NotoSansBold36); 259 | sprite.setTextDatum(MC_DATUM); 260 | sprite.setTextColor(TFT_SKYBLUE, TFT_BLACK); 261 | 262 | if (option == Charger) 263 | { 264 | sprite.drawString("Set charger on or off?", TFT_WIDTH / 2, TFT_HEIGHT / 2); 265 | RefreshDisplay(); 266 | } 267 | else 268 | { 269 | sprite.drawString("Set inverter on or off?", TFT_WIDTH / 2, TFT_HEIGHT / 2); 270 | RefreshDisplay(); 271 | }; 272 | 273 | // wait here for one second and also, if needed, for the user to release the button that they had previously pressed to get here 274 | msTimer.begin(1000); 275 | 276 | // ensure whichever button was last pressed is now released 277 | while (digitalRead(topButton) == 0) 278 | msTimer.begin(50); 279 | 280 | while (digitalRead(bottomButton) == 0) 281 | msTimer.begin(50); 282 | 283 | if (IsKeepDisplayOnTimedOut()) 284 | { 285 | 286 | if (generalDebugOutput) 287 | Serial.println("Timed out waiting for the user to release the button, no change will be applied"); 288 | 289 | // keep the display on for one minute 290 | SetKeepDisplayOnTimeOut(1); 291 | 292 | return; 293 | }; 294 | 295 | // prompt for desired state 296 | 297 | int xPosition; 298 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 299 | { 300 | xPosition = 0; 301 | sprite.setTextDatum(TL_DATUM); 302 | } 303 | else 304 | { 305 | xPosition = TFT_WIDTH; 306 | sprite.setTextDatum(TR_DATUM); 307 | }; 308 | 309 | if (option == Charger) 310 | { 311 | if ((currentMultiplusMode == ChargerOnly) || (currentMultiplusMode == On)) 312 | { 313 | sprite.drawString("ON (current mode)", xPosition, 0); 314 | sprite.drawString("OFF", xPosition, TFT_HEIGHT - 30); 315 | } 316 | else 317 | { 318 | sprite.drawString("ON", xPosition, 0); 319 | sprite.drawString("OFF (current mode)", xPosition, TFT_HEIGHT - 30); 320 | } 321 | } 322 | else 323 | { 324 | if ((currentMultiplusMode == InverterOnly) || (currentMultiplusMode == On)) 325 | { 326 | sprite.drawString("ON (current mode)", xPosition, 0); 327 | sprite.drawString("OFF", xPosition, TFT_HEIGHT - 30); 328 | } 329 | else 330 | { 331 | sprite.drawString("ON", xPosition, 0); 332 | sprite.drawString("OFF (current mode)", xPosition, TFT_HEIGHT - 30); 333 | }; 334 | }; 335 | 336 | RefreshDisplay(); 337 | 338 | sprite.unloadFont(); 339 | 340 | // wait until a choice is made 341 | 342 | while ((digitalRead(topButton) != 0) && (digitalRead(bottomButton) != 0) && !IsKeepDisplayOnTimedOut()) 343 | msTimer.begin(100); 344 | 345 | if (IsKeepDisplayOnTimedOut()) 346 | { 347 | 348 | if (generalDebugOutput) 349 | Serial.println("Timed out waiting for the user to make a choice, no change will be applied"); 350 | 351 | SetKeepDisplayOnTimeOut(1); 352 | return; 353 | }; 354 | 355 | bool userChoseOn; 356 | 357 | if (digitalRead(topButton) == 0) 358 | { 359 | userChoseOn = true; 360 | if (generalDebugOutput) 361 | Serial.println("Choice is 'ON'"); 362 | } 363 | else 364 | { 365 | userChoseOn = false; 366 | if (generalDebugOutput) 367 | Serial.println("Choice is 'OFF'"); 368 | }; 369 | 370 | // ensure whichever button was last pressed is now released 371 | while (digitalRead(topButton) == 0) 372 | msTimer.begin(50); 373 | 374 | while (digitalRead(bottomButton) == 0) 375 | msTimer.begin(50); 376 | 377 | multiplusMode desiredMultiplusMode; 378 | desiredMultiplusMode = currentMultiplusMode; 379 | 380 | if (option == Charger) 381 | { 382 | 383 | if (userChoseOn) 384 | { 385 | 386 | switch (currentMultiplusMode) 387 | { 388 | case ChargerOnly: 389 | break; 390 | case InverterOnly: 391 | desiredMultiplusMode = On; 392 | break; 393 | case On: 394 | break; 395 | case Off: 396 | desiredMultiplusMode = ChargerOnly; 397 | break; 398 | } 399 | } 400 | else 401 | { 402 | switch (currentMultiplusMode) 403 | { 404 | case ChargerOnly: 405 | desiredMultiplusMode = Off; 406 | break; 407 | case InverterOnly: 408 | break; 409 | case On: 410 | desiredMultiplusMode = InverterOnly; 411 | break; 412 | case Off: 413 | break; 414 | }; 415 | }; 416 | }; 417 | 418 | if (option == Inverter) 419 | { 420 | 421 | if (userChoseOn) 422 | { 423 | 424 | switch (currentMultiplusMode) 425 | { 426 | case ChargerOnly: 427 | desiredMultiplusMode = On; 428 | break; 429 | case InverterOnly: 430 | break; 431 | case On: 432 | break; 433 | case Off: 434 | desiredMultiplusMode = InverterOnly; 435 | break; 436 | } 437 | } 438 | else 439 | { 440 | switch (currentMultiplusMode) 441 | { 442 | case ChargerOnly: 443 | break; 444 | case InverterOnly: 445 | desiredMultiplusMode = Off; 446 | break; 447 | case On: 448 | desiredMultiplusMode = ChargerOnly; 449 | break; 450 | case Off: 451 | break; 452 | }; 453 | }; 454 | }; 455 | 456 | // apply the change if required 457 | 458 | if (currentMultiplusMode == desiredMultiplusMode) 459 | { 460 | 461 | if (generalDebugOutput) 462 | Serial.println("No change required to the multiplus mode"); 463 | } 464 | else 465 | { 466 | 467 | String modeCodeValue; 468 | 469 | switch (desiredMultiplusMode) 470 | { 471 | 472 | case ChargerOnly: 473 | modeCodeValue = "1"; 474 | if (generalDebugOutput) 475 | Serial.println("Set multiplus mode to charger only"); 476 | break; 477 | case InverterOnly: 478 | modeCodeValue = "2"; 479 | if (generalDebugOutput) 480 | Serial.println("Set multiplus mode to inverter only"); 481 | break; 482 | case On: 483 | modeCodeValue = "3"; 484 | if (generalDebugOutput) 485 | Serial.println("Set multiplus mode to on"); 486 | break; 487 | case Off: 488 | modeCodeValue = "4"; 489 | if (generalDebugOutput) 490 | Serial.println("Set multiplus mode to off"); 491 | break; 492 | }; 493 | 494 | // set the Multiplus's mode to Unknown while it changes over 495 | // it will be reset to its current mode in the next MQTT publishing cycle 496 | currentMultiplusMode = Unknown; 497 | 498 | // change the mode 499 | client.publish("W/" + VictronInstallationID + "/vebus/" + MultiplusThreeDigitID + "/Mode", "{\"value\": " + modeCodeValue + "}"); 500 | delay(250); 501 | }; 502 | 503 | // keep the display on for one minute 504 | SetKeepDisplayOnTimeOut(1); 505 | }; 506 | 507 | void CheckButtons() 508 | { 509 | 510 | if (theDisplayIsCurrentlyOn) 511 | { 512 | 513 | if (GENERAL_SETTINGS_ALLOW_CHANGING_INVERTER_AND_CHARGER_MODES) 514 | { 515 | 516 | // The top button is used to turn on/off the charger 517 | // The bottom button is used to turn on/off the inverter 518 | 519 | if (digitalRead(topButton) == 0) 520 | ChangeMultiplusMode(Charger); 521 | 522 | if (digitalRead(bottomButton) == 0) 523 | ChangeMultiplusMode(Inverter); 524 | }; 525 | } 526 | else 527 | { 528 | 529 | if ((digitalRead(topButton) == 0) || (digitalRead(bottomButton) == 0)) 530 | { 531 | 532 | SetTheDisplayOn(true); 533 | 534 | // ensure whichever button was last pressed is now released 535 | while (digitalRead(topButton) == 0) 536 | msTimer.begin(50); 537 | 538 | while (digitalRead(bottomButton) == 0) 539 | msTimer.begin(50); 540 | 541 | SetKeepDisplayOnTimeOut(1); 542 | }; 543 | }; 544 | } 545 | 546 | String ConvertSecondsToDayHoursMinutes(int n) 547 | { 548 | 549 | String sDays, sHours, sMinutes; 550 | 551 | int day = n / (24 * 3600); 552 | 553 | if ((day) > 0) 554 | sDays = String(day); 555 | else 556 | sDays = ""; 557 | 558 | n = n % (24 * 3600); 559 | 560 | int hour = n / 3600; 561 | sHours = String(hour); 562 | 563 | n %= 3600; 564 | 565 | int minutes = n / 60; 566 | if (minutes < 10) 567 | sMinutes = "0" + String(minutes); 568 | else 569 | sMinutes = String(minutes); 570 | 571 | return (sDays + " " + sHours + ":" + sMinutes); 572 | // return (sDays + "D " + sHours + "H " + sMinutes + "M"); 573 | } 574 | 575 | float roundFloat(float value, int decimals) 576 | { 577 | float multiplier = pow(10, decimals); 578 | return round(value * multiplier) / multiplier; 579 | } 580 | 581 | String ConvertToStringWithAFixedNumberOfDecimalPlaces(float f, int numberOfDecimalPlaces = 1) 582 | { 583 | 584 | // if round = true then round to the specified number of decimal places, otherwise truncate to the specified number of decimal places 585 | 586 | char workingArray[20 + numberOfDecimalPlaces]; 587 | char formattedArray[20 + numberOfDecimalPlaces]; 588 | 589 | if (GENERAL_SETTINGS_ROUND_NUMBERS) 590 | f = roundFloat(f, numberOfDecimalPlaces); 591 | 592 | dtostrf(f, 15 + numberOfDecimalPlaces, numberOfDecimalPlaces, formattedArray); 593 | sprintf(workingArray, "%s", formattedArray); 594 | 595 | String returnValue = String(workingArray); 596 | 597 | returnValue.trim(); 598 | 599 | return returnValue; 600 | } 601 | 602 | void printLocalTime() 603 | { 604 | 605 | struct tm timeinfo; 606 | if (getLocalTime(&timeinfo)) 607 | { 608 | if (generalDebugOutput) 609 | Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S zone %Z %z "); 610 | } 611 | else 612 | { 613 | if (generalDebugOutput) 614 | Serial.println("Failed to obtain time 1"); 615 | }; 616 | } 617 | 618 | bool SetTime() 619 | { 620 | 621 | // Configure NTP Server 622 | configTime(0, 0, primaryNTPServer, secondaryNTPServer, tertiaryNTPSever); 623 | 624 | // Set the time zone 625 | if (setenv("TZ", timeZone, 1) != 0) 626 | { 627 | if (generalDebugOutput) 628 | Serial.println("Error setting time zone"); 629 | return false; 630 | }; 631 | 632 | tzset(); 633 | 634 | struct tm timeinfo; 635 | if (!getLocalTime(&timeinfo)) 636 | { 637 | if (generalDebugOutput) 638 | Serial.println("Failed to obtain time from time server"); 639 | return false; 640 | }; 641 | 642 | time_t t = mktime(&timeinfo); 643 | if (t == -1) 644 | { 645 | if (generalDebugOutput) 646 | Serial.println("Error converting time to time_t"); 647 | return false; 648 | }; 649 | 650 | struct timeval now = {.tv_sec = t}; 651 | if (settimeofday(&now, NULL) != 0) 652 | { 653 | if (generalDebugOutput) 654 | Serial.println("Error setting system time"); 655 | return false; 656 | }; 657 | 658 | if (generalDebugOutput) 659 | { 660 | if (generalDebugOutput) 661 | Serial.printf("Time set to: %s", asctime(&timeinfo)); 662 | }; 663 | 664 | if (generalDebugOutput) 665 | { 666 | // wait for console updates to complete 667 | const int timerInterval = 200; 668 | msTimer.begin(timerInterval); 669 | }; 670 | 671 | return true; 672 | } 673 | 674 | void SetKeepDisplayOnTimeOut(unsigned int minutes) 675 | { 676 | // Set the timeout after which IsKeepDisplayOnTimedOut() will become true 677 | keepDisplayOnTimeOut = (unsigned long)minutes * 60UL * 1000UL; 678 | ResetKeepDisplayOnStartTime(); 679 | 680 | if (generalDebugOutput) 681 | { 682 | Serial.print("Keep display active for this many minutes: "); 683 | Serial.println(minutes); 684 | }; 685 | } 686 | 687 | void ResetKeepDisplayOnStartTime() 688 | { 689 | // Reset the start time of the display timeout to now 690 | keepDisplayOnStartTime = millis(); 691 | } 692 | 693 | bool IsKeepDisplayOnTimedOut() 694 | { 695 | // Returns true if the display timeout time has passed and false when not. 696 | if (millis() - keepDisplayOnStartTime >= keepDisplayOnTimeOut) 697 | { 698 | return true; 699 | } 700 | return false; 701 | } 702 | 703 | void RefreshTimeOnceADay(bool forceTimeSet = false) 704 | { 705 | 706 | const int RETRY_INTERVAL_IN_MINUTES = 20; 707 | const unsigned long RETRY_INTERVAL_IN_MILLIS = RETRY_INTERVAL_IN_MINUTES * 60UL * 1000UL; 708 | const unsigned long ONE_DAY_IN_MILLIS = 24UL * 60UL * 60UL * 1000UL; 709 | static unsigned long lastTimeCheck = 0UL; 710 | static unsigned long checkInterval = 0UL; 711 | 712 | if (forceTimeSet || (millis() - lastTimeCheck >= checkInterval)) 713 | { 714 | lastTimeCheck = millis(); 715 | if (SetTime()) 716 | { 717 | checkInterval = ONE_DAY_IN_MILLIS; 718 | } 719 | else 720 | { 721 | checkInterval = RETRY_INTERVAL_IN_MILLIS; 722 | // keep the display on until the time can be successfully set from the NTP server 723 | SetKeepDisplayOnTimeOut(RETRY_INTERVAL_IN_MINUTES + 1); 724 | }; 725 | }; 726 | }; 727 | 728 | bool ShouldTheDisplayBeOn() 729 | { 730 | 731 | int timeToWake = wakeHour * 60 + wakeMinute; 732 | int timeToSleep = sleepHour * 60 + sleepMinute; 733 | 734 | static int lastMinuteChecked = -1; 735 | static bool theDisplayHadBeenPreviouslyKeptOn = true; 736 | static bool lastReturnValue = true; 737 | 738 | bool returnValue = lastReturnValue; 739 | 740 | if (turnOnDisplayAtSpecificTimesOnly) 741 | { 742 | 743 | if (timeToWake == timeToSleep) 744 | { 745 | 746 | // the display should only be set to on if the KeepDisplayOnTimeOut has not happened yet 747 | returnValue = IsKeepDisplayOnTimedOut(); 748 | } 749 | else 750 | { 751 | 752 | if (!IsKeepDisplayOnTimedOut()) 753 | { 754 | 755 | theDisplayHadBeenPreviouslyKeptOn = true; 756 | returnValue = true; 757 | } 758 | else 759 | { 760 | 761 | // Get the utc time 762 | time_t utc_now = time(nullptr); 763 | 764 | // the detailed time checking logic below is only performed: 765 | // once a minute when the seconds first reach zero 766 | // or 767 | // when the keepTheDisplayTimeOut had previously been kept on but no longer needs to be given the passing of time 768 | // otherwise 769 | // return the previously returned value 770 | 771 | int iSecond = second(utc_now); 772 | 773 | if ((iSecond == 0) || (theDisplayHadBeenPreviouslyKeptOn)) 774 | { 775 | 776 | theDisplayHadBeenPreviouslyKeptOn = false; 777 | 778 | // get the current local time 779 | tm *timeinfo = localtime(&utc_now); 780 | 781 | int iMinute = timeinfo->tm_min; 782 | 783 | if (iMinute == lastMinuteChecked) 784 | 785 | returnValue = lastReturnValue; 786 | 787 | else 788 | { 789 | 790 | lastMinuteChecked = iMinute; 791 | 792 | int iHour = timeinfo->tm_hour; 793 | 794 | if (timeToSleep > timeToWake) 795 | { 796 | 797 | // as an example: wake up at 8 am and go to sleep at 10 pm 798 | 799 | if ((iHour < wakeHour) || ((iHour == wakeHour) && (iMinute < wakeMinute)) || (iHour > sleepHour) || ((iHour == sleepHour) && (iMinute >= sleepMinute))) 800 | returnValue = false; 801 | else 802 | returnValue = true; 803 | } 804 | else 805 | { 806 | 807 | // as an example: wake up at 10 pm and go to sleep at 8 am 808 | 809 | if ((iHour < sleepHour) || ((iHour == sleepHour) && (iMinute < sleepMinute)) || (iHour > wakeHour) || ((iHour == wakeHour) && (iMinute >= wakeMinute))) 810 | returnValue = true; 811 | else 812 | returnValue = false; 813 | }; 814 | }; 815 | }; 816 | }; 817 | }; 818 | 819 | lastReturnValue = returnValue; 820 | }; 821 | 822 | return returnValue; 823 | } 824 | 825 | void UpdateDisplay() 826 | { 827 | 828 | static unsigned long lastDisplayUpdate = 0UL; 829 | static bool tryToRestoreConnection = true; 830 | static bool MQTTTransmissionLost = false; 831 | 832 | // turn on or off the display as needed 833 | bool theDisplayShouldBeOn = ShouldTheDisplayBeOn(); 834 | 835 | if (theDisplayIsCurrentlyOn && !theDisplayShouldBeOn) 836 | SetTheDisplayOn(false); 837 | 838 | if (!theDisplayIsCurrentlyOn && theDisplayShouldBeOn) 839 | SetTheDisplayOn(true); 840 | 841 | // only update the display when its time has come 842 | if (millis() - lastDisplayUpdate < ((unsigned long)GENERAL_SETTINGS_SECONDS_BETWEEN_DISPLAY_UPDATES * 1000UL)) 843 | return; 844 | 845 | // only update the display if it is on 846 | if (!theDisplayShouldBeOn) 847 | return; 848 | 849 | // if the connection is not yet established, or had been lost then display an appropriate message 850 | 851 | if (!client.isWifiConnected()) 852 | { 853 | 854 | sprite.fillSprite(TFT_BLACK); 855 | sprite.loadFont(NotoSansBold36); 856 | sprite.setTextDatum(MC_DATUM); 857 | sprite.setTextColor(TFT_SKYBLUE, TFT_BLACK); 858 | sprite.drawString("Awaiting Wi-Fi connection", TFT_WIDTH / 2, TFT_HEIGHT / 2); 859 | RefreshDisplay(); 860 | sprite.unloadFont(); 861 | return; 862 | }; 863 | 864 | if (!client.isMqttConnected()) 865 | { 866 | 867 | sprite.fillSprite(TFT_BLACK); 868 | sprite.loadFont(NotoSansBold36); 869 | sprite.setTextDatum(MC_DATUM); 870 | sprite.setTextColor(TFT_SKYBLUE, TFT_BLACK); 871 | sprite.drawString("Awaiting MQTT connection", TFT_WIDTH / 2, TFT_HEIGHT / 2); 872 | RefreshDisplay(); 873 | sprite.unloadFont(); 874 | return; 875 | }; 876 | 877 | // deal with the case that no data has arrived beyond the timeout period 878 | // see the notes in the general_settings.h file for more information 879 | 880 | if (millis() - lastMQTTUpdateReceived >= timeOutInMilliSeconds) 881 | { 882 | 883 | sprite.fillSprite(TFT_BLACK); 884 | sprite.loadFont(NotoSansBold24); 885 | sprite.setTextDatum(MC_DATUM); 886 | sprite.setTextColor(TFT_RED, TFT_BLACK); 887 | sprite.drawString("MQTT data updates have stopped", TFT_WIDTH / 2, TFT_HEIGHT / 2); 888 | RefreshDisplay(); 889 | 890 | sprite.unloadFont(); 891 | 892 | if (!GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS) 893 | { 894 | // another system is responsible for sending the keep alive requests 895 | // keep the message "MQTT data updates have stopped" on the screen for a brief period 896 | // note: although the code below only delays for 1 second, the message will stay on the screen 897 | // for longer than that while attempts (below) are made to reconnect 898 | delay(1000); 899 | }; 900 | 901 | if (tryToRestoreConnection) 902 | { 903 | 904 | // only try to restore the connection once 905 | tryToRestoreConnection = false; 906 | 907 | if (generalDebugOutput) 908 | Serial.println("MQTT data updates have stopped"); 909 | 910 | ResetGlobals(); 911 | 912 | if (!GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS) 913 | { 914 | 915 | // if GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS is false it means that this program was counting on another system to send 916 | // the keep alive request. However, as this does not seem to be happening at the moment, the program will try resubscribing and sending 917 | // a keep alive request itself (which is part of the mass subscribe process) to temporarily get things going again 918 | 919 | MQTTTransmissionLost = true; 920 | 921 | if (generalDebugOutput) 922 | Serial.println("Attempting to restore MQTT data updates"); 923 | 924 | MassSubscribe(); 925 | }; 926 | }; 927 | return; 928 | }; 929 | 930 | tryToRestoreConnection = true; 931 | 932 | // wait until data for all data points have been received prior to showing the display 933 | // while waiting display an appropriate message 934 | 935 | if (awaitingInitialTransmissionOfAllDataPoints) 936 | { 937 | 938 | for (int i = 0; i < dataPoints; i++) 939 | if (awaitingDataToBeReceived[i]) 940 | { 941 | 942 | if (verboseDebugOutput) 943 | Serial.println("Awaiting data on data point " + String(i)); 944 | 945 | sprite.fillSprite(TFT_BLACK); 946 | sprite.loadFont(NotoSansBold36); 947 | sprite.setTextDatum(MC_DATUM); 948 | sprite.setTextColor(TFT_SKYBLUE, TFT_BLACK); 949 | sprite.drawString("Awaiting data", TFT_WIDTH / 2, TFT_HEIGHT / 2); 950 | RefreshDisplay(); 951 | sprite.unloadFont(); 952 | 953 | return; 954 | }; 955 | // if we have reached this point data for all data points have been received 956 | awaitingInitialTransmissionOfAllDataPoints = false; 957 | 958 | if (MQTTTransmissionLost) 959 | { 960 | MQTTTransmissionLost = false; 961 | if (generalDebugOutput) 962 | Serial.println("MQTT data updates restored"); 963 | }; 964 | }; 965 | 966 | lastDisplayUpdate = millis(); 967 | 968 | int x, y; 969 | 970 | // Tabula rasa 971 | 972 | sprite.fillSprite(TFT_BLACK); 973 | 974 | // show charger and inverter status 975 | 976 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 977 | sprite.setTextDatum(TL_DATUM); 978 | else 979 | sprite.setTextDatum(TR_DATUM); 980 | 981 | sprite.loadFont(NotoSansBold24); 982 | sprite.setTextColor(TFT_SKYBLUE, TFT_BLACK); 983 | 984 | String chargerStatus; 985 | if (currentMultiplusMode == Unknown) 986 | chargerStatus = "?"; 987 | else if ((currentMultiplusMode == On) || (currentMultiplusMode == ChargerOnly)) 988 | chargerStatus = "on"; 989 | else 990 | chargerStatus = "off"; 991 | 992 | String inverterStatus; 993 | if (currentMultiplusMode == Unknown) 994 | inverterStatus = "?"; 995 | else if ((currentMultiplusMode == On) || (currentMultiplusMode == InverterOnly)) 996 | inverterStatus = "on"; 997 | else 998 | inverterStatus = "off"; 999 | 1000 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 1001 | x = 0; 1002 | else 1003 | x = TFT_WIDTH; 1004 | 1005 | if (GENERAL_SETTINGS_SHOW_CHARGER_MODE) 1006 | { 1007 | y = 5; 1008 | sprite.drawString("Charger " + chargerStatus, x, y); 1009 | }; 1010 | 1011 | if (GENERAL_SETTINGS_SHOW_INVERTER_MODE) 1012 | { 1013 | y = TFT_HEIGHT - 30; 1014 | sprite.drawString("Inverter " + inverterStatus, x, y); 1015 | }; 1016 | 1017 | sprite.unloadFont(); 1018 | 1019 | // show solar info 1020 | 1021 | sprite.loadFont(NotoSansBold24); 1022 | sprite.setTextColor(TFT_YELLOW, TFT_BLACK); 1023 | 1024 | y = TFT_HEIGHT / 2 - 36; 1025 | sprite.drawString("Solar", x, y); 1026 | 1027 | sprite.loadFont(NotoSansBold36); 1028 | solarWatts = int(solarWatts); 1029 | 1030 | y = TFT_HEIGHT / 2 + 4; 1031 | if (GENERAL_SETTINGS_IF_OVER_1000_WATTS_REPORT_KW && (solarWatts >= 1000.0F)) 1032 | { 1033 | float adjustedSolarWatts = solarWatts / 1000.0F; 1034 | sprite.drawString(ConvertToStringWithAFixedNumberOfDecimalPlaces(adjustedSolarWatts, GENERAL_SETTINGS_NUMBER_DECIMAL_PLACES_FOR_KW_REPORTING) + " KW", x, y); 1035 | } 1036 | else 1037 | { 1038 | sprite.drawString(String(int(solarWatts)) + " W", x, y); 1039 | }; 1040 | 1041 | sprite.unloadFont(); 1042 | 1043 | // show grid info 1044 | 1045 | sprite.loadFont(NotoSansBold24); 1046 | sprite.setTextColor(TFT_GOLD, TFT_BLACK); 1047 | 1048 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 1049 | sprite.setTextDatum(TR_DATUM); 1050 | else 1051 | sprite.setTextDatum(TL_DATUM); 1052 | 1053 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 1054 | x = TFT_WIDTH; 1055 | else 1056 | x = 0; 1057 | 1058 | y = 0; 1059 | sprite.drawString("Grid", x, y); 1060 | sprite.unloadFont(); 1061 | 1062 | sprite.loadFont(NotoSansBold36); 1063 | 1064 | float totalGridWatts = int(gridInL1Watts) + int(gridInL2Watts) + int(gridInL3Watts); 1065 | 1066 | y = 43; 1067 | if (GENERAL_SETTINGS_IF_OVER_1000_WATTS_REPORT_KW && (totalGridWatts >= 1000.0F)) 1068 | { 1069 | float adjustedTotalGridWatts = totalGridWatts / 1000.0F; 1070 | sprite.drawString(ConvertToStringWithAFixedNumberOfDecimalPlaces(adjustedTotalGridWatts, GENERAL_SETTINGS_NUMBER_DECIMAL_PLACES_FOR_KW_REPORTING) + " KW", x, y); 1071 | } 1072 | else 1073 | { 1074 | totalGridWatts = int(totalGridWatts); 1075 | sprite.drawString(String(int(totalGridWatts)) + " W", x, y); 1076 | }; 1077 | 1078 | sprite.unloadFont(); 1079 | 1080 | // show AC consumption info 1081 | 1082 | sprite.loadFont(NotoSansBold24); 1083 | sprite.setTextColor(TFT_SILVER, TFT_BLACK); 1084 | 1085 | y = TFT_HEIGHT - 73; 1086 | sprite.drawString("AC Load", x, y); 1087 | 1088 | sprite.unloadFont(); 1089 | sprite.loadFont(NotoSansBold36); 1090 | 1091 | y = TFT_HEIGHT - 30; 1092 | float totalACConsumptionWatts = int(ACOutL1Watts + ACOutL2Watts + ACOutL3Watts); 1093 | if (GENERAL_SETTINGS_IF_OVER_1000_WATTS_REPORT_KW && (totalACConsumptionWatts >= 1000.0F)) 1094 | { 1095 | totalACConsumptionWatts = totalACConsumptionWatts / 1000.0F; 1096 | sprite.drawString(ConvertToStringWithAFixedNumberOfDecimalPlaces(totalACConsumptionWatts, GENERAL_SETTINGS_NUMBER_DECIMAL_PLACES_FOR_KW_REPORTING) + " KW", x, y); 1097 | } 1098 | else 1099 | { 1100 | totalACConsumptionWatts = int(totalACConsumptionWatts); 1101 | sprite.drawString(String(int(totalACConsumptionWatts)) + " W", x, y); 1102 | }; 1103 | 1104 | sprite.unloadFont(); 1105 | 1106 | // show battery info 1107 | 1108 | int midX, midY, outerRadius, innerRadius, startAngle, endAngle; 1109 | unsigned short batteryColour; 1110 | 1111 | midX = TFT_WIDTH / 2; 1112 | midY = TFT_HEIGHT / 2; 1113 | 1114 | if (midX < midY) 1115 | outerRadius = midX; 1116 | else 1117 | outerRadius = midY; 1118 | 1119 | innerRadius = outerRadius - 8; 1120 | 1121 | startAngle = 180; 1122 | endAngle = int(batterySOC * 3.6 + 180) % 360; 1123 | 1124 | if (batterySOC <= GENERAL_SETTINGS_SHOW_BATTERY_AS_RED) 1125 | { 1126 | batteryColour = TFT_RED; 1127 | } 1128 | else if (batterySOC <= GENERAL_SETTINGS_SHOW_BATTERY_AS_YELLOW) 1129 | { 1130 | batteryColour = TFT_YELLOW; 1131 | } 1132 | else 1133 | { 1134 | batteryColour = TFT_GREEN; 1135 | }; 1136 | 1137 | sprite.drawSmoothArc(midX, midY, outerRadius, innerRadius, startAngle, endAngle, batteryColour, TFT_BLACK); 1138 | 1139 | sprite.loadFont(NotoSansBold72); 1140 | sprite.setTextDatum(MC_DATUM); 1141 | sprite.setTextColor(batteryColour, TFT_BLACK); 1142 | 1143 | // show battery percent without a decimal place 1144 | 1145 | int ibatterySOC = ConvertToStringWithAFixedNumberOfDecimalPlaces(batterySOC, 0).toInt(); 1146 | sprite.drawString(String(ibatterySOC) + "%", midX, midY); 1147 | 1148 | sprite.unloadFont(); 1149 | 1150 | sprite.loadFont(NotoSansBold24); 1151 | sprite.drawString("Battery", midX, midY - 60); 1152 | 1153 | if ((GENERAL_SETTINGS_ADDITIONAL_INFO == 1) && (batteryTTG != 0)) 1154 | { 1155 | 1156 | // show time to go 1157 | String TimeToGo = ConvertSecondsToDayHoursMinutes(int(batteryTTG)); 1158 | sprite.drawString(TimeToGo, midX, midY + 50); 1159 | } 1160 | else if (GENERAL_SETTINGS_ADDITIONAL_INFO == 2) 1161 | { 1162 | 1163 | // show charger state 1164 | sprite.drawString(chargingState, midX, midY + 50); 1165 | } 1166 | else if (GENERAL_SETTINGS_ADDITIONAL_INFO == 3) 1167 | { 1168 | 1169 | // show battery temperature (with one decimal place) 1170 | 1171 | String temperatureString = ConvertToStringWithAFixedNumberOfDecimalPlaces(batteryTemperature, 1) + String(" "); 1172 | 1173 | sprite.drawString(temperatureString, midX, midY + 50); 1174 | 1175 | // add the degree symbol 1176 | sprite.unloadFont(); 1177 | sprite.loadFont(NotoSansBold15); 1178 | int degreePosx = sprite.textWidth(temperatureString) / 2 + 4; 1179 | sprite.drawString(String("o"), midX + degreePosx, midY + 43); 1180 | } 1181 | else if (GENERAL_SETTINGS_ADDITIONAL_INFO == 4) 1182 | { 1183 | 1184 | //show battery power 1185 | if (GENERAL_SETTINGS_IF_OVER_1000_WATTS_REPORT_KW && ((batteryPower >= 1000.0F) || (batteryPower <= -1000.0F))) { 1186 | sprite.drawString(ConvertToStringWithAFixedNumberOfDecimalPlaces(batteryPower / 1000.0F, GENERAL_SETTINGS_NUMBER_DECIMAL_PLACES_FOR_KW_REPORTING) + " kW", midX, midY + 50); 1187 | } else { 1188 | sprite.drawString(String(int(batteryPower)) + " W", midX, midY + 50); 1189 | }; 1190 | } 1191 | 1192 | sprite.unloadFont(); 1193 | 1194 | // Draw an upward triangle if the battery is charging or a downward triangle if it is discharging 1195 | // However, if it is neither charging or discharging then do not draw any triangle at all 1196 | 1197 | if (batteryPower > 0.0F) 1198 | { 1199 | 1200 | // Draw a upward triangle 1201 | int16_t centerX = TFT_WIDTH / 2; 1202 | int16_t centerY = TFT_HEIGHT / 2 + 88; 1203 | 1204 | int16_t arrowWidth = 20; 1205 | int16_t arrowHeight = 25; 1206 | 1207 | sprite.fillTriangle(centerX - arrowWidth / 2, centerY + arrowHeight / 2, 1208 | centerX + arrowWidth / 2, centerY + arrowHeight / 2, 1209 | centerX, centerY - arrowHeight / 2, batteryColour); 1210 | } 1211 | else 1212 | { 1213 | 1214 | if (batteryPower < 0.0F) 1215 | { 1216 | 1217 | // Draw a downward triangle 1218 | int16_t centerX = TFT_WIDTH / 2; 1219 | int16_t centerY = TFT_HEIGHT / 2 + 88; 1220 | 1221 | int16_t arrowWidth = 20; 1222 | int16_t arrowHeight = 25; 1223 | 1224 | sprite.fillTriangle(centerX - arrowWidth / 2, centerY - arrowHeight / 2, 1225 | centerX + arrowWidth / 2, centerY - arrowHeight / 2, 1226 | centerX, centerY + arrowHeight / 2, batteryColour); 1227 | }; 1228 | }; 1229 | 1230 | // used for testing only - display the date and time 1231 | // 1232 | // sprite.loadFont(NotoSansBold24); 1233 | // sprite.setTextColor(TFT_RED, TFT_BLACK); 1234 | // 1235 | // if (GENERAL_SETTINGS_USB_ON_THE_LEFT) { 1236 | // sprite.setTextDatum(TL_DATUM); 1237 | //} else { 1238 | // sprite.setTextDatum(TR_DATUM); 1239 | //}; 1240 | // 1241 | // struct tm timeinfo; 1242 | // getLocalTime(&timeinfo); 1243 | // String currentTimeString = (String)asctime(&timeinfo) + " "; 1244 | // 1245 | // sprite.drawString(currentTimeString, 0, 50); 1246 | // sprite.unloadFont(); 1247 | // 1248 | // end of testing block 1249 | 1250 | RefreshDisplay(); 1251 | } 1252 | 1253 | void ResetGlobals() 1254 | { 1255 | 1256 | if (initialStartupLoadVictronInstallationAndMultiplusIDs) 1257 | { 1258 | 1259 | initialStartupLoadVictronInstallationAndMultiplusIDs = false; 1260 | 1261 | String(SECRET_SETTING_VICTRON_INSTALLATION_ID).toCharArray(VictronInstallationIDArray, String(SECRET_SETTING_VICTRON_INSTALLATION_ID).length() + 1); 1262 | String(SECRET_SETTING_VICTRON_MULTIPLUS_ID).toCharArray(MultiplusThreeDigitIDArray, String(SECRET_SETTING_VICTRON_MULTIPLUS_ID).length() + 1); 1263 | String(SECRET_SETTING_VICTRON_SOLAR_CHARGER_ID).toCharArray(SolarChargerThreeDigitIDArray, String(SECRET_SETTING_VICTRON_SOLAR_CHARGER_ID).length() + 1); 1264 | }; 1265 | 1266 | VictronInstallationID = String(VictronInstallationIDArray); 1267 | MultiplusThreeDigitID = String(MultiplusThreeDigitIDArray); 1268 | SolarChargerThreeDigitID = String(SolarChargerThreeDigitIDArray); 1269 | 1270 | awaitingInitialTransmissionOfAllDataPoints = true; 1271 | for (int i = 0; i < dataPoints; i++) 1272 | awaitingDataToBeReceived[i] = true; 1273 | 1274 | gridInL1Watts = 0.0; 1275 | gridInL2Watts = 0.0; 1276 | gridInL3Watts = 0.0; 1277 | 1278 | solarWatts = 0.0; 1279 | 1280 | batterySOC = 0.0; 1281 | batteryTTG = 0.0; 1282 | batteryPower = 0.0; 1283 | 1284 | ACOutL1Watts = 0.0; 1285 | ACOutL2Watts = 0.0; 1286 | ACOutL3Watts = 0.0; 1287 | 1288 | currentMultiplusMode = Unknown; 1289 | 1290 | chargingState = ""; 1291 | }; 1292 | 1293 | void KeepMQTTAlive(bool forceKeepAliveRequestNow = false) 1294 | { 1295 | 1296 | static unsigned long lastMqttUpdate = 0UL; 1297 | 1298 | if ((forceKeepAliveRequestNow) || (GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS)) 1299 | { 1300 | 1301 | if ((forceKeepAliveRequestNow) || (millis() - lastMqttUpdate >= GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS_INTERVAL)) 1302 | { 1303 | 1304 | lastMqttUpdate = millis(); 1305 | 1306 | client.publish("R/" + VictronInstallationID + "/keepalive", ""); 1307 | 1308 | if (verboseDebugOutput) 1309 | Serial.println("Keep alive request sent"); 1310 | 1311 | msTimer.begin(100); 1312 | }; 1313 | }; 1314 | } 1315 | 1316 | void SubscribeToGetChargingStateFromMultiplus() 1317 | { 1318 | 1319 | String commonTopic = "N/" + VictronInstallationID; 1320 | String ledsTopic = commonTopic + "/vebus/" + MultiplusThreeDigitID + "/Leds"; 1321 | 1322 | client.subscribe(ledsTopic + "/Bulk", [](const String &payload) 1323 | { 1324 | awaitingDataToBeReceived[7] = false; 1325 | String response = String(payload); 1326 | JsonDocument doc; 1327 | DeserializationError error = deserializeJson(doc, response); 1328 | int LEDIndicator = doc["value"].as(); 1329 | if (LEDIndicator == 1) 1330 | chargingState = "Bulk"; 1331 | if (verboseDebugOutput) 1332 | Serial.println("Multiplus Bulk LED is on"); 1333 | lastMQTTUpdateReceived = millis(); 1334 | doc.clear(); }); 1335 | 1336 | msTimer.begin(100); 1337 | 1338 | client.subscribe(ledsTopic + "/Absorption", [](const String &payload) 1339 | { 1340 | awaitingDataToBeReceived[7] = false; 1341 | String response = String(payload); 1342 | JsonDocument doc; 1343 | DeserializationError error = deserializeJson(doc, response); 1344 | int LEDIndicator = doc["value"].as(); 1345 | if (LEDIndicator == 1) 1346 | chargingState = "Absorption"; 1347 | if (verboseDebugOutput) 1348 | Serial.println("Multiplus Absorption LED is on"); 1349 | lastMQTTUpdateReceived = millis(); 1350 | doc.clear(); }); 1351 | 1352 | msTimer.begin(100); 1353 | 1354 | client.subscribe(ledsTopic + "/Float", [](const String &payload) 1355 | { 1356 | awaitingDataToBeReceived[7] = false; 1357 | String response = String(payload); 1358 | JsonDocument doc; 1359 | DeserializationError error = deserializeJson(doc, response); 1360 | int LEDIndicator = doc["value"].as(); 1361 | if (LEDIndicator == 1) 1362 | chargingState = "Float"; 1363 | if (verboseDebugOutput) 1364 | Serial.println("Multiplus Float LED is on"); 1365 | lastMQTTUpdateReceived = millis(); 1366 | doc.clear(); }); 1367 | 1368 | msTimer.begin(100); 1369 | 1370 | KeepMQTTAlive(true); 1371 | } 1372 | 1373 | void SubscribeToGetChargingStateFromSolarCharger() 1374 | { 1375 | 1376 | String commonTopic = "N/" + VictronInstallationID; 1377 | String solarChargerStateTopic = commonTopic + "/solarcharger/" + SolarChargerThreeDigitID + "/State"; 1378 | 1379 | client.subscribe(solarChargerStateTopic, [](const String &payload) 1380 | { 1381 | String response = String(payload); 1382 | JsonDocument doc; 1383 | DeserializationError error = deserializeJson(doc, response); 1384 | int stateCode = doc["value"].as(); 1385 | 1386 | switch (stateCode) { 1387 | case 0: 1388 | chargingState = "Off"; 1389 | break; 1390 | case 2: 1391 | chargingState = "Fault"; 1392 | break; 1393 | case 3: 1394 | chargingState = "Bulk"; 1395 | break; 1396 | case 4: 1397 | chargingState = "Absorption"; 1398 | break; 1399 | case 5: 1400 | chargingState = "Float"; 1401 | break; 1402 | case 6: 1403 | chargingState = "Storage"; 1404 | break; 1405 | case 7: 1406 | chargingState = "Equalize"; 1407 | break; 1408 | case 252: 1409 | // ESS is being used, so we will get the Charging State from the Multiplus instead 1410 | // this will be done outside this switch statement to keep the compiler happy 1411 | ESSIsBeingUsed = true; 1412 | chargingState = ""; 1413 | break; 1414 | default: 1415 | chargingState = "Unknown"; 1416 | break; 1417 | }; 1418 | 1419 | if (ESSIsBeingUsed) { 1420 | 1421 | String commonTopic = "N/" + VictronInstallationID; 1422 | String xsolarChargerStateTopic = commonTopic + "/solarcharger/" + SolarChargerThreeDigitID + "/State"; 1423 | 1424 | client.unsubscribe(xsolarChargerStateTopic); 1425 | SubscribeToGetChargingStateFromMultiplus(); 1426 | } else { 1427 | awaitingDataToBeReceived[7] = false; 1428 | if (verboseDebugOutput) 1429 | Serial.println("Charging State from MPPT: " + chargingState); 1430 | }; 1431 | 1432 | lastMQTTUpdateReceived = millis(); 1433 | doc.clear(); }); 1434 | } 1435 | 1436 | void MassSubscribe() 1437 | { 1438 | 1439 | // at this point we have the VictronInstallationID, MultiplusThreeDigitID and SolarChargerThreeDigitID so let's get the rest of the data 1440 | 1441 | if (generalDebugOutput) 1442 | Serial.println("Subscribing"); 1443 | 1444 | String commonTopic = "N/" + VictronInstallationID; 1445 | String system0Topic = commonTopic + "/system/0/"; 1446 | String multiplusModeTopic = commonTopic + "/vebus/" + MultiplusThreeDigitID + "/Mode"; 1447 | 1448 | // reset global variables so we will not start displaying information until all the subscribed data has been received 1449 | ResetGlobals(); 1450 | 1451 | // get the data 1452 | 1453 | // Grid (L1, L2, L3) 1454 | 1455 | if (GENERAL_SETTINGS_GRID_IN_L1_IS_USED) 1456 | { 1457 | 1458 | client.subscribe(system0Topic + "Ac/Grid/L1/Power", [](const String &payload) 1459 | { 1460 | awaitingDataToBeReceived[0] = false; 1461 | String response = String(payload); 1462 | JsonDocument doc; 1463 | DeserializationError error = deserializeJson(doc, response); 1464 | gridInL1Watts = doc["value"].as(); 1465 | if (verboseDebugOutput) 1466 | Serial.println("gridInL1Watts: " + String(gridInL1Watts)); 1467 | lastMQTTUpdateReceived = millis(); 1468 | doc.clear(); }); 1469 | 1470 | msTimer.begin(100); 1471 | } 1472 | else 1473 | { 1474 | awaitingDataToBeReceived[0] = false; 1475 | }; 1476 | 1477 | if (GENERAL_SETTINGS_GRID_IN_L2_IS_USED) 1478 | { 1479 | 1480 | client.subscribe(system0Topic + "Ac/Grid/L2/Power", [](const String &payload) 1481 | { 1482 | awaitingDataToBeReceived[1] = false; 1483 | String response = String(payload); 1484 | JsonDocument doc; 1485 | DeserializationError error = deserializeJson(doc, response); 1486 | gridInL2Watts = doc["value"].as(); 1487 | if (verboseDebugOutput) 1488 | Serial.println("gridInL2Watts: " + String(gridInL2Watts)); 1489 | lastMQTTUpdateReceived = millis(); 1490 | doc.clear(); }); 1491 | 1492 | msTimer.begin(100); 1493 | } 1494 | else 1495 | { 1496 | awaitingDataToBeReceived[1] = false; 1497 | }; 1498 | 1499 | if (GENERAL_SETTINGS_GRID_IN_L3_IS_USED) 1500 | { 1501 | 1502 | client.subscribe(system0Topic + "Ac/Grid/L3/Power", [](const String &payload) 1503 | { 1504 | awaitingDataToBeReceived[2] = false; 1505 | String response = String(payload); 1506 | JsonDocument doc; 1507 | DeserializationError error = deserializeJson(doc, response); 1508 | gridInL3Watts = doc["value"].as(); 1509 | if (verboseDebugOutput) 1510 | Serial.println("gridInL3Watts: " + String(gridInL3Watts)); 1511 | lastMQTTUpdateReceived = millis(); 1512 | doc.clear(); }); 1513 | 1514 | msTimer.begin(100); 1515 | } 1516 | else 1517 | { 1518 | awaitingDataToBeReceived[2] = false; 1519 | }; 1520 | 1521 | // Solar 1522 | if (GENERAL_SETTINGS_PV_IS_USED) 1523 | { 1524 | client.subscribe(system0Topic + "Dc/Pv/Power", [](const String &payload) 1525 | { 1526 | awaitingDataToBeReceived[3] = false; 1527 | String response = String(payload); 1528 | JsonDocument doc; 1529 | DeserializationError error = deserializeJson(doc, response); 1530 | solarWatts = doc["value"].as(); 1531 | if (verboseDebugOutput) 1532 | Serial.println("solarWatts: " + String(solarWatts)); 1533 | lastMQTTUpdateReceived = millis(); 1534 | doc.clear(); }); 1535 | 1536 | msTimer.begin(100); 1537 | } 1538 | else 1539 | { 1540 | awaitingDataToBeReceived[3] = false; 1541 | }; 1542 | 1543 | // Battery 1544 | 1545 | client.subscribe(system0Topic + "Dc/Battery/Soc", [](const String &payload) 1546 | { 1547 | awaitingDataToBeReceived[4] = false; 1548 | String response = String(payload); 1549 | JsonDocument doc; 1550 | DeserializationError error = deserializeJson(doc, response); 1551 | batterySOC = doc["value"].as(); 1552 | if (verboseDebugOutput) 1553 | Serial.println("batterySOC: " + String(batterySOC)); 1554 | lastMQTTUpdateReceived = millis(); 1555 | doc.clear(); }); 1556 | 1557 | msTimer.begin(100); 1558 | 1559 | client.subscribe(system0Topic + "Dc/Battery/Power", [](const String &payload) 1560 | { 1561 | awaitingDataToBeReceived[5] = false; 1562 | String response = String(payload); 1563 | JsonDocument doc; 1564 | DeserializationError error = deserializeJson(doc, response); 1565 | batteryPower = doc["value"].as(); 1566 | if (verboseDebugOutput) 1567 | Serial.println("batteryPower: " + String(batteryPower)); 1568 | lastMQTTUpdateReceived = millis(); 1569 | doc.clear(); }); 1570 | 1571 | msTimer.begin(100); 1572 | 1573 | switch (GENERAL_SETTINGS_ADDITIONAL_INFO) 1574 | { 1575 | 1576 | case 0: 1577 | 1578 | awaitingDataToBeReceived[6] = false; 1579 | awaitingDataToBeReceived[7] = false; 1580 | awaitingDataToBeReceived[8] = false; 1581 | break; 1582 | 1583 | case 1: 1584 | 1585 | client.subscribe(system0Topic + "Dc/Battery/TimeToGo", [](const String &payload) 1586 | { 1587 | awaitingDataToBeReceived[6] = false; 1588 | String response = String(payload); 1589 | JsonDocument doc; 1590 | DeserializationError error = deserializeJson(doc, response); 1591 | batteryTTG = doc["value"].as(); 1592 | if (verboseDebugOutput) 1593 | Serial.println("batteryTTG: " + String(batteryTTG)); 1594 | lastMQTTUpdateReceived = millis(); 1595 | doc.clear(); }); 1596 | 1597 | awaitingDataToBeReceived[7] = false; 1598 | awaitingDataToBeReceived[8] = false; 1599 | break; 1600 | 1601 | case 2: 1602 | 1603 | awaitingDataToBeReceived[6] = false; 1604 | 1605 | if (ESSIsBeingUsed) 1606 | SubscribeToGetChargingStateFromMultiplus(); 1607 | else 1608 | SubscribeToGetChargingStateFromSolarCharger(); 1609 | 1610 | awaitingDataToBeReceived[8] = false; 1611 | break; 1612 | 1613 | case 3: 1614 | 1615 | awaitingDataToBeReceived[6] = false; 1616 | awaitingDataToBeReceived[7] = false; 1617 | 1618 | client.subscribe(system0Topic + "Dc/Battery/Temperature", [](const String &payload) 1619 | { 1620 | awaitingDataToBeReceived[8] = false; 1621 | String response = String(payload); 1622 | JsonDocument doc; 1623 | DeserializationError error = deserializeJson(doc, response); 1624 | batteryTemperature = doc["value"].as(); 1625 | if (verboseDebugOutput) 1626 | Serial.println("batteryTemperature: " + String(batteryTemperature)); 1627 | lastMQTTUpdateReceived = millis(); 1628 | doc.clear(); }); 1629 | 1630 | msTimer.begin(100); 1631 | break; 1632 | 1633 | case 4: 1634 | 1635 | awaitingDataToBeReceived[6] = false; 1636 | awaitingDataToBeReceived[7] = false; 1637 | awaitingDataToBeReceived[8] = false; 1638 | break; 1639 | 1640 | default: 1641 | break; 1642 | }; 1643 | 1644 | msTimer.begin(100); 1645 | 1646 | // AC Out (L1, L2, L3) 1647 | 1648 | if (GENERAL_SETTINGS_AC_OUT_L1_IS_USED) 1649 | { 1650 | client.subscribe(system0Topic + "Ac/Consumption/L1/Power", [](const String &payload) 1651 | { 1652 | awaitingDataToBeReceived[9] = false; 1653 | String response = String(payload); 1654 | JsonDocument doc; 1655 | DeserializationError error = deserializeJson(doc, response); 1656 | ACOutL1Watts = doc["value"].as(); 1657 | if (verboseDebugOutput) 1658 | Serial.println("ACOutL1Watts: " + String(ACOutL1Watts)); 1659 | lastMQTTUpdateReceived = millis(); 1660 | doc.clear(); }); 1661 | 1662 | msTimer.begin(100); 1663 | } 1664 | else 1665 | { 1666 | awaitingDataToBeReceived[9] = false; 1667 | }; 1668 | 1669 | if (GENERAL_SETTINGS_AC_OUT_L2_IS_USED) 1670 | { 1671 | client.subscribe(system0Topic + "Ac/Consumption/L2/Power", [](const String &payload) 1672 | { 1673 | awaitingDataToBeReceived[10] = false; 1674 | String response = String(payload); 1675 | JsonDocument doc; 1676 | DeserializationError error = deserializeJson(doc, response); 1677 | ACOutL2Watts = doc["value"].as(); 1678 | if (verboseDebugOutput) 1679 | Serial.println("ACOutL2Watts: " + String(ACOutL2Watts)); 1680 | lastMQTTUpdateReceived = millis(); 1681 | doc.clear(); }); 1682 | 1683 | msTimer.begin(100); 1684 | } 1685 | else 1686 | { 1687 | awaitingDataToBeReceived[10] = false; 1688 | }; 1689 | 1690 | if (GENERAL_SETTINGS_AC_OUT_L3_IS_USED) 1691 | { 1692 | client.subscribe(system0Topic + "Ac/Consumption/L3/Power", [](const String &payload) 1693 | { 1694 | awaitingDataToBeReceived[11] = false; 1695 | String response = String(payload); 1696 | JsonDocument doc; 1697 | DeserializationError error = deserializeJson(doc, response); 1698 | ACOutL3Watts = doc["value"].as(); 1699 | if (verboseDebugOutput) 1700 | Serial.println("ACOutL3Watts: " + String(ACOutL3Watts)); 1701 | lastMQTTUpdateReceived = millis(); 1702 | doc.clear(); }); 1703 | 1704 | msTimer.begin(100); 1705 | } 1706 | else 1707 | { 1708 | awaitingDataToBeReceived[11] = false; 1709 | }; 1710 | 1711 | // Multiplus mode 1712 | 1713 | currentMultiplusMode = Unknown; 1714 | 1715 | client.subscribe(multiplusModeTopic, [](const String &payload) 1716 | { 1717 | awaitingDataToBeReceived[12] = false; 1718 | String response = String(payload); 1719 | JsonDocument doc; 1720 | DeserializationError error = deserializeJson(doc, response); 1721 | int workingMode = doc["value"].as(); 1722 | switch (workingMode) { 1723 | case 1: 1724 | currentMultiplusMode = ChargerOnly; 1725 | if (verboseDebugOutput) 1726 | Serial.println("Multiplus is in charger only mode"); 1727 | break; 1728 | case 2: 1729 | currentMultiplusMode = InverterOnly; 1730 | if (verboseDebugOutput) 1731 | Serial.println("Multiplus is in inverter only mode"); 1732 | break; 1733 | case 3: 1734 | currentMultiplusMode = On; 1735 | if (verboseDebugOutput) 1736 | Serial.println("Multiplus is in on"); 1737 | break; 1738 | case 4: 1739 | currentMultiplusMode = Off; 1740 | if (verboseDebugOutput) 1741 | Serial.println("Multiplus is off"); 1742 | break; 1743 | default: 1744 | currentMultiplusMode = Unknown; 1745 | if (verboseDebugOutput) 1746 | Serial.println("Unknown multiplus mode: " + String(workingMode)); 1747 | break; 1748 | }; 1749 | lastMQTTUpdateReceived = millis(); 1750 | doc.clear(); }); 1751 | 1752 | msTimer.begin(100); 1753 | 1754 | KeepMQTTAlive(true); 1755 | } 1756 | 1757 | void MassUnsubscribe() 1758 | { 1759 | 1760 | if (generalDebugOutput) 1761 | Serial.println("Unsubscribing"); 1762 | 1763 | String commonTopic = "N/" + VictronInstallationID; 1764 | String system0Topic = commonTopic + "/system/0/"; 1765 | String multiplusModeTopic = commonTopic + "/vebus/" + MultiplusThreeDigitID + "/Mode"; 1766 | String solarChargerStateTopic = commonTopic + "/solarcharger/" + SolarChargerThreeDigitID + "/State"; 1767 | String ledsTopic = commonTopic + "/vebus/" + MultiplusThreeDigitID + "/Leds/"; 1768 | 1769 | if (GENERAL_SETTINGS_GRID_IN_L1_IS_USED) 1770 | client.unsubscribe(system0Topic + "Ac/Grid/L1/Power"); 1771 | 1772 | if (GENERAL_SETTINGS_GRID_IN_L2_IS_USED) 1773 | client.unsubscribe(system0Topic + "Ac/Grid/L2/Power"); 1774 | 1775 | if (GENERAL_SETTINGS_GRID_IN_L3_IS_USED) 1776 | client.unsubscribe(system0Topic + "Ac/Grid/L3/Power"); 1777 | 1778 | if (GENERAL_SETTINGS_PV_IS_USED) 1779 | client.unsubscribe(system0Topic + "Dc/Pv/Power"); 1780 | 1781 | client.unsubscribe(system0Topic + "Dc/Battery/Soc"); 1782 | 1783 | client.unsubscribe(system0Topic + "Dc/Battery/Power"); 1784 | 1785 | switch (GENERAL_SETTINGS_ADDITIONAL_INFO) 1786 | { 1787 | case 1: 1788 | client.unsubscribe(system0Topic + "Dc/Battery/TimeToGo"); 1789 | break; 1790 | case 2: 1791 | if (ESSIsBeingUsed) 1792 | { 1793 | client.unsubscribe(ledsTopic + "Bulk"); 1794 | client.unsubscribe(ledsTopic + "Absorption"); 1795 | client.unsubscribe(ledsTopic + "Float"); 1796 | } 1797 | else 1798 | client.unsubscribe(solarChargerStateTopic); 1799 | break; 1800 | case 3: 1801 | client.unsubscribe(system0Topic + "Dc/Battery/Temperature"); 1802 | break; 1803 | default: 1804 | break; 1805 | }; 1806 | 1807 | if (GENERAL_SETTINGS_AC_OUT_L1_IS_USED) 1808 | client.unsubscribe(system0Topic + "Ac/Consumption/L1/Power"); 1809 | 1810 | if (GENERAL_SETTINGS_AC_OUT_L2_IS_USED) 1811 | client.unsubscribe(system0Topic + "Ac/Consumption/L2/Power"); 1812 | 1813 | if (GENERAL_SETTINGS_AC_OUT_L3_IS_USED) 1814 | client.unsubscribe(system0Topic + "Ac/Consumption/L3/Power"); 1815 | 1816 | client.unsubscribe(multiplusModeTopic); 1817 | 1818 | msTimer.begin(100); 1819 | } 1820 | 1821 | void onConnectionEstablished() 1822 | { 1823 | 1824 | // note: this subroutine uses recursion to discover the VictronInstallationID, MultiplusThreeDigitID and (if needed) SolarChargerThreeDigitID 1825 | 1826 | if (VictronInstallationID == "+") 1827 | { 1828 | 1829 | // Let's find the Victron Installation ID 1830 | 1831 | client.subscribe("N/+/system/0/Serial", [](const String &topic, const String &payload) 1832 | { 1833 | client.unsubscribe("N/+/system/0/Serial"); 1834 | String mytopic = String(topic); 1835 | VictronInstallationID = mytopic.substring(2, 14); 1836 | VictronInstallationID.toCharArray(VictronInstallationIDArray, VictronInstallationID.length() + 1); 1837 | if (generalDebugOutput) 1838 | Serial.println("*** Discovered Installation ID: " + VictronInstallationID); 1839 | 1840 | onConnectionEstablished(); 1841 | return; }); 1842 | 1843 | return; 1844 | }; 1845 | 1846 | if (MultiplusThreeDigitID == "+") 1847 | { 1848 | 1849 | // Let's find the Multiplus three digit ID 1850 | String commonTopic = "N/" + VictronInstallationID; 1851 | client.subscribe(commonTopic + "/vebus/+/Mode", [](const String &topic, const String &payload) 1852 | { 1853 | String commonTopic = "N/" + VictronInstallationID; 1854 | client.unsubscribe(commonTopic + "/vebus/+/Mode"); 1855 | String mytopic = String(topic); 1856 | MultiplusThreeDigitID = mytopic.substring(21, 24); 1857 | MultiplusThreeDigitID.toCharArray(MultiplusThreeDigitIDArray, MultiplusThreeDigitID.length() + 1); 1858 | if (generalDebugOutput) 1859 | Serial.println("*** Discovered Multiplus three digit ID: " + MultiplusThreeDigitID); 1860 | 1861 | onConnectionEstablished(); 1862 | return; }); 1863 | 1864 | // a keep alive request is required for Venus to publish the topic subscribed to above 1865 | KeepMQTTAlive(true); 1866 | 1867 | return; 1868 | }; 1869 | 1870 | if ((SolarChargerThreeDigitID == "+") && (GENERAL_SETTINGS_ADDITIONAL_INFO == 2)) 1871 | { 1872 | 1873 | // Let's find the solarcharger three digit ID 1874 | String commonTopic = "N/" + VictronInstallationID; 1875 | client.subscribe(commonTopic + "/solarcharger/+/Mode", [](const String &topic, const String &payload) 1876 | { 1877 | String commonTopic = "N/" + VictronInstallationID; 1878 | client.unsubscribe(commonTopic + "/solarcharger/+/Mode"); 1879 | String mytopic = String(topic); 1880 | SolarChargerThreeDigitID = mytopic.substring(28, 31); 1881 | SolarChargerThreeDigitID.toCharArray(SolarChargerThreeDigitIDArray, SolarChargerThreeDigitID.length() + 1); 1882 | if (generalDebugOutput) 1883 | Serial.println("*** Discovered Solar Charger three digit ID: " + SolarChargerThreeDigitID); 1884 | 1885 | onConnectionEstablished(); 1886 | return; }); 1887 | 1888 | // a keep alive request is required for Venus to publish the topic subscribed to above 1889 | KeepMQTTAlive(true); 1890 | 1891 | return; 1892 | }; 1893 | 1894 | // at this point the recursive calling is over and we have the VictronInstallationID, MultiplusThreeDigitID and (if needed) SolarChargerThreeDigitID so let's get the rest of the data 1895 | 1896 | MassSubscribe(); 1897 | } 1898 | 1899 | void onWiFiConnectionEstablished(WiFiEvent_t event, WiFiEventInfo_t info) 1900 | { 1901 | 1902 | if (generalDebugOutput) 1903 | { 1904 | 1905 | Serial.print("Wi-Fi connected to: "); 1906 | Serial.println(String(SECRET_SETTINGS_WIFI_SSID)); 1907 | 1908 | Serial.print("IP address: "); 1909 | Serial.println(WiFi.localIP()); 1910 | }; 1911 | 1912 | RefreshTimeOnceADay(true); 1913 | } 1914 | 1915 | void SetupWiFiAndMQTT() 1916 | { 1917 | 1918 | if (generalDebugOutput) 1919 | Serial.println("Setting up Wi-Fi and MQTT"); 1920 | 1921 | // if GENERAL_SETTINGS_TURN_ON_DISPLAY_AT_SPECIFIC_TIMES_ONLY is true 1922 | // then enable the on-connection event in order that the time from an NTP server once the Wifi connection has been established 1923 | // otherwise this is not needed 1924 | if (GENERAL_SETTINGS_TURN_ON_DISPLAY_AT_SPECIFIC_TIMES_ONLY) 1925 | WiFi.onEvent(onWiFiConnectionEstablished, WiFiEvent_t::ARDUINO_EVENT_WIFI_STA_GOT_IP); 1926 | 1927 | if (GENERAL_SETTINGS_ENABLE_OVER_THE_AIR_UPDATES) 1928 | client.enableOTA(SECRET_SETTINGS_OTA_PASSWORD, SECRET_SETTINGS_OTA_Port); 1929 | 1930 | if (verboseDebugOutput) 1931 | client.enableDebuggingMessages(); 1932 | } 1933 | 1934 | bool isNumeric(String str) 1935 | { 1936 | 1937 | for (size_t i = 0; i < str.length(); i++) 1938 | { 1939 | if (!isdigit(str.charAt(i))) 1940 | { 1941 | return false; 1942 | } 1943 | } 1944 | return true; 1945 | } 1946 | 1947 | void convertSecondsToTime(int seconds, int &hours, int &minutes, int &remainingSeconds) 1948 | { 1949 | hours = seconds / 3600; // 3600 seconds in an hour 1950 | seconds %= 3600; 1951 | minutes = seconds / 60; // 60 seconds in a minute 1952 | remainingSeconds = seconds % 60; 1953 | } 1954 | 1955 | void GotoDeepSleep() 1956 | { 1957 | 1958 | // this routine is only called when it is time to send the ESP32 to sleep 1959 | // according the logic below counts on the fact that the current time is currently within the sleep period 1960 | 1961 | int toleranceSeconds = 15; // don't bother going to sleep if a wakeup would otherwise happen in this many seconds 1962 | 1963 | int secondsInDeepSleep = 0; 1964 | 1965 | String sleepTime = String(GENERAL_SETTINGS_SLEEP_TIME); 1966 | 1967 | String wakeTime = String(GENERAL_SETTINGS_WAKE_TIME); 1968 | 1969 | if (sleepTime == wakeTime) 1970 | { 1971 | 1972 | // if sleep time = wake time go into deep sleep, 1973 | // however do not set a timer to wake up, rather wake will be handled by a button press 1974 | 1975 | MassUnsubscribe(); 1976 | 1977 | if (generalDebugOutput) 1978 | { 1979 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 1980 | Serial.print("Going to sleep; device may be awakened by pressing the top button"); 1981 | else 1982 | Serial.print("Going to sleep; device may be awakened by pressing the bottom button"); 1983 | delay(250); 1984 | Serial.flush(); 1985 | }; 1986 | 1987 | // provide time for the mass subscribe to complete 1988 | msTimer.begin(1000); 1989 | 1990 | esp_deep_sleep_start(); 1991 | } 1992 | else 1993 | { 1994 | 1995 | if (parseTimeString(sleepTime, sleepHour, sleepMinute)) 1996 | { 1997 | 1998 | if (parseTimeString(wakeTime, wakeHour, wakeMinute)) 1999 | { 2000 | 2001 | int wakeupTimeInSeconds = (wakeHour * 60 + wakeMinute) * 60; 2002 | 2003 | int sleepTimeInSeconds = (sleepHour * 60 + sleepMinute) * 60; 2004 | 2005 | int NowInSeconds; 2006 | 2007 | struct tm timeinfo; 2008 | if (getLocalTime(&timeinfo)) 2009 | { 2010 | NowInSeconds = (timeinfo.tm_hour * 60 + timeinfo.tm_min) * 60 + timeinfo.tm_sec; 2011 | } 2012 | else 2013 | { 2014 | 2015 | int tryAgainInThisManyMinutes = 10; 2016 | SetKeepDisplayOnTimeOut(tryAgainInThisManyMinutes); 2017 | if (generalDebugOutput) 2018 | { 2019 | Serial.println("Could not set wakeup timer - network may be down - try again in another "); 2020 | Serial.println(tryAgainInThisManyMinutes); 2021 | Serial.println(" minutes"); 2022 | }; 2023 | return; 2024 | }; 2025 | 2026 | int secondsToDeepSleep; 2027 | 2028 | if (wakeupTimeInSeconds < sleepTimeInSeconds) 2029 | { 2030 | 2031 | if (NowInSeconds == wakeupTimeInSeconds) 2032 | { 2033 | secondsInDeepSleep = 0; 2034 | } 2035 | else 2036 | { 2037 | 2038 | // the current time must be either before wakeup time or after sleep time 2039 | 2040 | if (NowInSeconds < wakeupTimeInSeconds) 2041 | { 2042 | secondsInDeepSleep = wakeupTimeInSeconds - NowInSeconds; 2043 | } 2044 | else 2045 | { 2046 | int secondsInADay = 24 * 60 * 60; 2047 | secondsInDeepSleep = secondsInADay - NowInSeconds + wakeupTimeInSeconds; 2048 | }; 2049 | }; 2050 | } 2051 | else 2052 | { 2053 | 2054 | // wakeupTimeInSeconds > sleepTimeInSeconds 2055 | 2056 | if (NowInSeconds != wakeupTimeInSeconds) 2057 | { 2058 | 2059 | // the current time must be between the sleep time and the wakeup time 2060 | secondsInDeepSleep = wakeupTimeInSeconds - NowInSeconds; 2061 | }; 2062 | }; 2063 | }; 2064 | }; 2065 | }; 2066 | 2067 | if (secondsInDeepSleep > toleranceSeconds) 2068 | { 2069 | 2070 | MassUnsubscribe(); 2071 | 2072 | if (generalDebugOutput) 2073 | { 2074 | 2075 | int hours, minutes, remainingSeconds; 2076 | convertSecondsToTime(secondsInDeepSleep, hours, minutes, remainingSeconds); 2077 | 2078 | Serial.print("Going to sleep for "); 2079 | 2080 | Serial.print(hours); 2081 | if (hours == 1) 2082 | Serial.print(" hour "); 2083 | else 2084 | Serial.print(" hours "); 2085 | 2086 | Serial.print(minutes); 2087 | if (minutes == 1) 2088 | Serial.print(" minute "); 2089 | else 2090 | Serial.print(" minutes "); 2091 | 2092 | Serial.print(remainingSeconds); 2093 | if (remainingSeconds == 1) 2094 | Serial.println(" second"); 2095 | else 2096 | Serial.println(" seconds"); 2097 | 2098 | delay(250); 2099 | Serial.flush(); 2100 | }; 2101 | 2102 | // provide time for the mass subscribe to complete 2103 | msTimer.begin(1000); 2104 | 2105 | const uint64_t convertSecondsToMicroSeconds = 1000000; 2106 | unsigned long long DeepSleepMicroSeconds = secondsInDeepSleep * convertSecondsToMicroSeconds; 2107 | esp_sleep_enable_timer_wakeup(DeepSleepMicroSeconds); 2108 | esp_deep_sleep_start(); 2109 | } 2110 | else 2111 | { 2112 | 2113 | if (generalDebugOutput) 2114 | { 2115 | Serial.print("No need to sleep, as it'll be time to wakeup in the next "); 2116 | Serial.print(toleranceSeconds); 2117 | Serial.println(" seconds anyway!"); 2118 | }; 2119 | }; 2120 | } 2121 | 2122 | void SetTheDisplayOn(bool theDisplayShouldBeOn) 2123 | { 2124 | 2125 | static bool firstTimeSetup = true; 2126 | 2127 | if (theDisplayIsCurrentlyOn && !theDisplayShouldBeOn) 2128 | { 2129 | 2130 | if (GENERAL_SETTINGS_USE_DEEP_SLEEP) 2131 | { 2132 | 2133 | GotoDeepSleep(); 2134 | } 2135 | else 2136 | { 2137 | 2138 | // an AMOLED screen has no backlight as each pixel is an individual LED, so filling the display with black effectively turns it off causing it to use the least current possible 2139 | sprite.fillSprite(TFT_BLACK); 2140 | RefreshDisplay(); 2141 | 2142 | // if the display is off there is no use receiving MQTT information, therefore unsubscribe to it so network traffic may be reduced 2143 | MassUnsubscribe(); 2144 | 2145 | theDisplayIsCurrentlyOn = false; 2146 | 2147 | if (generalDebugOutput) 2148 | Serial.println("Display turned off"); 2149 | }; 2150 | } 2151 | else 2152 | { 2153 | 2154 | if (!theDisplayIsCurrentlyOn && theDisplayShouldBeOn) 2155 | { 2156 | 2157 | // turn the display on 2158 | // effectively just re-enable updates to it 2159 | 2160 | // if this is the first time the display is being turned on then 2161 | // subscribe so that the data will be updated now that the display has been turned back on 2162 | if (firstTimeSetup) 2163 | firstTimeSetup = false; 2164 | else 2165 | MassSubscribe(); 2166 | 2167 | theDisplayIsCurrentlyOn = true; 2168 | 2169 | if (generalDebugOutput) 2170 | Serial.println("Display turned on"); 2171 | }; 2172 | }; 2173 | } 2174 | 2175 | void SetDisplayOrientation() 2176 | { 2177 | 2178 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 2179 | lcd_setRotation(3); 2180 | else 2181 | lcd_setRotation(1); 2182 | }; 2183 | 2184 | bool parseTimeString(String timeString, int &hours, int &minutes) 2185 | { 2186 | 2187 | if (timeString.length() != 5) 2188 | { // Check if the length is not equal to "HH:MM" 2189 | return false; 2190 | } 2191 | 2192 | if (timeString.charAt(2) != ':') 2193 | { // Check if the separator is at the correct position 2194 | return false; 2195 | } 2196 | 2197 | String hourStr = timeString.substring(0, 2); // Extract hours substring 2198 | String minuteStr = timeString.substring(3); // Extract minutes substring 2199 | 2200 | if (!isNumeric(hourStr) || !isNumeric(minuteStr)) 2201 | { // Check if both substrings are numeric 2202 | return false; 2203 | } 2204 | 2205 | hours = hourStr.toInt(); // Convert hour string to integer 2206 | minutes = minuteStr.toInt(); // Convert minute string to integer 2207 | 2208 | if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) 2209 | { // Check if hours and minutes are within valid range 2210 | return false; 2211 | } 2212 | 2213 | return true; // Time string successfully parsed 2214 | } 2215 | 2216 | void SetDisplayOnAndOffTimes() 2217 | { 2218 | 2219 | if (turnOnDisplayAtSpecificTimesOnly) 2220 | { 2221 | 2222 | String sleepTime = String(GENERAL_SETTINGS_SLEEP_TIME); 2223 | String wakeTime = String(GENERAL_SETTINGS_WAKE_TIME); 2224 | 2225 | if (parseTimeString(sleepTime, sleepHour, sleepMinute)) 2226 | { 2227 | 2228 | if (parseTimeString(wakeTime, wakeHour, wakeMinute)) 2229 | { 2230 | 2231 | int timeToWake = wakeHour * 60 + wakeMinute; 2232 | int timeToSleep = sleepHour * 60 + sleepMinute; 2233 | theDisplayIsCurrentlyOn = (timeToWake < timeToSleep); 2234 | 2235 | if (verboseDebugOutput) 2236 | { 2237 | if (timeToWake < timeToSleep) 2238 | { 2239 | Serial.print("Wake time set to: "); 2240 | Serial.println(wakeTime); 2241 | Serial.print("Sleep time set to: "); 2242 | Serial.println(sleepTime); 2243 | } 2244 | else 2245 | { 2246 | Serial.print("Sleep time set to: "); 2247 | Serial.println(sleepTime); 2248 | Serial.print("Wake time set to: "); 2249 | Serial.println(wakeTime); 2250 | }; 2251 | }; 2252 | } 2253 | else 2254 | { 2255 | turnOnDisplayAtSpecificTimesOnly = false; 2256 | if (generalDebugOutput) 2257 | Serial.println("Problem with GENERAL_SETTINGS_WAKE_TIME setting value, should be HH:MM"); 2258 | } 2259 | } 2260 | else 2261 | { 2262 | turnOnDisplayAtSpecificTimesOnly = false; 2263 | if (generalDebugOutput) 2264 | Serial.println("Problem with GENERAL_SETTINGS_SLEEP_TIME setting value, should be HH:MM"); 2265 | }; 2266 | } 2267 | else 2268 | { 2269 | if (generalDebugOutput) 2270 | Serial.println("Display set as always on"); 2271 | }; 2272 | }; 2273 | 2274 | void SetupDisplay() 2275 | { 2276 | 2277 | sprite.createSprite(TFT_WIDTH, TFT_HEIGHT); 2278 | sprite.setSwapBytes(1); 2279 | 2280 | rm67162_init(); 2281 | 2282 | SetDisplayOrientation(); 2283 | 2284 | SetDisplayOnAndOffTimes(); 2285 | 2286 | SetKeepDisplayOnTimeOut(1); 2287 | 2288 | SetTheDisplayOn(true); 2289 | } 2290 | 2291 | void ShowOpeningWindow() 2292 | { 2293 | 2294 | if ((GENERAL_SETTINGS_SHOW_SPLASH_SCREEN) && (initialStartupShowSplashScreen)) 2295 | { 2296 | 2297 | sprite.fillSprite(TFT_BLACK); 2298 | 2299 | sprite.loadFont(NotoSansBold36); 2300 | sprite.setTextDatum(MC_DATUM); 2301 | sprite.setTextColor(TFT_SKYBLUE, TFT_BLACK); 2302 | sprite.drawString(programName, TFT_WIDTH / 2, TFT_HEIGHT / 2 - 50); 2303 | sprite.loadFont(NotoSansBold24); 2304 | sprite.drawString(programVersion, TFT_WIDTH / 2, TFT_HEIGHT / 2); 2305 | sprite.loadFont(NotoSansBold15); 2306 | sprite.drawString(programURL, TFT_WIDTH / 2, TFT_HEIGHT / 2 + 45); 2307 | 2308 | RefreshDisplay(); 2309 | sprite.unloadFont(); 2310 | 2311 | delay(5000); 2312 | 2313 | initialStartupShowSplashScreen = false; 2314 | }; 2315 | } 2316 | 2317 | void SetDebugLevel() 2318 | { 2319 | 2320 | generalDebugOutput = (GENERAL_SETTINGS_DEBUG_OUTPUT_LEVEL > 0); 2321 | verboseDebugOutput = (GENERAL_SETTINGS_DEBUG_OUTPUT_LEVEL > 1); 2322 | 2323 | if (generalDebugOutput) 2324 | { 2325 | Serial.begin(GENERAL_SETTINGS_SERIAL_MONITOR_SPEED); 2326 | }; 2327 | }; 2328 | 2329 | void SetWakeUpButton() 2330 | { 2331 | 2332 | if ((GENERAL_SETTINGS_TURN_ON_DISPLAY_AT_SPECIFIC_TIMES_ONLY) && (GENERAL_SETTINGS_USE_DEEP_SLEEP)) 2333 | { 2334 | 2335 | // set wakeup button; sadly while the button tied to GPIO 0 can be used for this, the button tied to GPIO 21 cannot 2336 | esp_sleep_enable_ext0_wakeup(GPIO_NUM_0, 0); // set wake-up on button controlled by GPIO 0 2337 | 2338 | if (generalDebugOutput) 2339 | { 2340 | if (GENERAL_SETTINGS_USB_ON_THE_LEFT) 2341 | Serial.println("Wakeup button is on the top"); 2342 | else 2343 | Serial.println("Wakeup button is on the bottom"); 2344 | }; 2345 | }; 2346 | }; 2347 | 2348 | void setup() 2349 | { 2350 | 2351 | SetDebugLevel(); 2352 | 2353 | if (generalDebugOutput) 2354 | { 2355 | 2356 | Serial.println(""); 2357 | Serial.println(programName + " " + programVersion); 2358 | Serial.println(programURL); 2359 | Serial.println(""); 2360 | }; 2361 | 2362 | if (GENERAL_SETTINGS_AMOLED_VERSION == 1) 2363 | { 2364 | SetGreenLEDOff(); 2365 | } 2366 | else 2367 | { 2368 | SetDisplayOn(); 2369 | }; 2370 | 2371 | SetupTopAndBottomButtons(); 2372 | 2373 | SetWakeUpButton(); 2374 | 2375 | SetupDisplay(); 2376 | 2377 | ResetGlobals(); 2378 | 2379 | SetupWiFiAndMQTT(); 2380 | 2381 | ShowOpeningWindow(); 2382 | } 2383 | 2384 | void loop() 2385 | { 2386 | 2387 | client.loop(); 2388 | 2389 | KeepMQTTAlive(); 2390 | 2391 | CheckButtons(); 2392 | 2393 | UpdateDisplay(); 2394 | 2395 | RefreshTimeOnceADay(); 2396 | 2397 | ArduinoOTA.handle(); 2398 | } 2399 | -------------------------------------------------------------------------------- /ESP32RemoteForVictron/fonts/NotoSansBold15.h: -------------------------------------------------------------------------------- 1 | /* The font vlw file can be converted to a byte array using: 2 | 3 | https://tomeko.net/online_tools/file_to_hex.php?lang=en 4 | 5 | Paste the byte array into a sketch tab and add two lines 6 | at the start with a unique font name: 7 | 8 | #include 9 | const uint8_t fontName[] PROGMEM = { 10 | 11 | At the end add: 12 | 13 | }; 14 | 15 | See example below. Include the tab in the main sketch, e.g.: 16 | 17 | #include "NotoSansBold15.h" 18 | */ 19 | 20 | #include 21 | 22 | const uint8_t NotoSansBold15[] PROGMEM = { 23 | 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 24 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x0C, 25 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 26 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x06, 27 | 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 28 | 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 29 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 30 | 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0C, 31 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00, 0x0C, 32 | 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 33 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 34 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 35 | 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 36 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 37 | 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0B, 38 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x0E, 39 | 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 41 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 43 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 44 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x02, 45 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2D, 0x00, 0x00, 0x00, 0x02, 46 | 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 47 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 48 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 49 | 0x00, 0x00, 0x00, 0x2F, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 50 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 51 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 52 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00, 0x00, 0x0B, 53 | 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 54 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 55 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 56 | 0x00, 0x00, 0x00, 0x33, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 57 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x34, 58 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 59 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x00, 0x00, 0x0C, 60 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 61 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x09, 62 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 63 | 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 64 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 65 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 66 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x39, 0x00, 0x00, 0x00, 0x0C, 67 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 69 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 70 | 0x00, 0x00, 0x00, 0x3B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 71 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 72 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 73 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0x00, 0x00, 0x00, 0x05, 74 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 75 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 76 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 77 | 0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 78 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 79 | 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0B, 80 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x00, 0x00, 0x00, 0x0B, 81 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 83 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 84 | 0x00, 0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 85 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x44, 86 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0B, 87 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x45, 0x00, 0x00, 0x00, 0x0B, 88 | 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 89 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 90 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 91 | 0x00, 0x00, 0x00, 0x47, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 92 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 93 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0B, 94 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x49, 0x00, 0x00, 0x00, 0x0B, 95 | 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 96 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4A, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x06, 97 | 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0B, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 98 | 0x00, 0x00, 0x00, 0x4B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 99 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4C, 100 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0B, 101 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4D, 0x00, 0x00, 0x00, 0x0B, 102 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 103 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 104 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 105 | 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 106 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 107 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 108 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x0E, 109 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 110 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 111 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 112 | 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 113 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 114 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 115 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x0C, 116 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 117 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 118 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 119 | 0x00, 0x00, 0x00, 0x57, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x0F, 120 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 121 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 122 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x00, 0x00, 0x00, 0x0B, 123 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 124 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x09, 125 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 126 | 0x00, 0x00, 0x00, 0x5B, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 127 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 128 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0B, 129 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0x00, 0x00, 0x00, 0x0E, 130 | 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 131 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5E, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 132 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 133 | 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 134 | 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 135 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0C, 136 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x61, 0x00, 0x00, 0x00, 0x0A, 137 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 138 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x08, 139 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 140 | 0x00, 0x00, 0x00, 0x63, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 141 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 142 | 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0C, 143 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x00, 0x0A, 144 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 145 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x07, 146 | 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 147 | 0x00, 0x00, 0x00, 0x67, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 148 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 149 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x0C, 150 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x00, 0x00, 0x00, 0x0C, 151 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 152 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6A, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 153 | 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x6B, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 155 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 156 | 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x0C, 157 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00, 0x09, 158 | 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 159 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6E, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x08, 160 | 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 161 | 0x00, 0x00, 0x00, 0x6F, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 162 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 163 | 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 164 | 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x0D, 165 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 166 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x72, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x06, 167 | 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 168 | 0x00, 0x00, 0x00, 0x73, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x07, 169 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x74, 170 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0A, 171 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x0A, 172 | 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x01, 173 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 174 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 175 | 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0D, 176 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 177 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 178 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x0D, 179 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 180 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 181 | 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 182 | 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 183 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 184 | 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 185 | 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7D, 0x00, 0x00, 0x00, 0x0E, 186 | 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 187 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x08, 188 | 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 189 | 0x00, 0x00, 0x00, 0xB0, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x06, 190 | 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xAE, 0xAE, 0x3D, 191 | 0x02, 0xFF, 0xFF, 0x52, 0x00, 0xFC, 0xFF, 0x4C, 0x00, 0xF6, 0xFF, 0x48, 0x00, 0xEE, 0xFF, 0x3F, 192 | 0x00, 0xD4, 0xFF, 0x15, 0x00, 0xB6, 0xFF, 0x08, 0x00, 0x41, 0x5D, 0x00, 0x00, 0x26, 0x39, 0x00, 193 | 0x06, 0xF6, 0xFF, 0x4C, 0x06, 0xEE, 0xFF, 0x3F, 0x00, 0x04, 0x17, 0x00, 0xAC, 0xAC, 0x00, 0xA5, 194 | 0xAE, 0x04, 0xF4, 0xF0, 0x00, 0xD6, 0xFC, 0x00, 0xDA, 0xDA, 0x00, 0xB4, 0xF4, 0x00, 0xB4, 0xB2, 195 | 0x00, 0xAC, 0xE7, 0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x59, 0xA7, 0x02, 196 | 0x48, 0xA7, 0x06, 0x00, 0x00, 0x00, 0x00, 0xAA, 0xEE, 0x00, 0xA3, 0xF4, 0x00, 0x00, 0x00, 0x00, 197 | 0x00, 0xDF, 0xAE, 0x00, 0xC9, 0xB6, 0x00, 0x00, 0x08, 0xAE, 0xAE, 0xFF, 0xDF, 0xAE, 0xFC, 0xE1, 198 | 0xAE, 0x3B, 0x08, 0xBB, 0xD0, 0xFF, 0xCE, 0xCE, 0xFF, 0xCE, 0xBB, 0x3F, 0x00, 0x00, 0x66, 0xFF, 199 | 0x1F, 0x5B, 0xFF, 0x2A, 0x00, 0x00, 0x41, 0x63, 0xC3, 0xFA, 0x63, 0xC1, 0xFC, 0x63, 0x5D, 0x00, 200 | 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEB, 0x00, 0x00, 0x02, 0xFC, 0x96, 0x02, 0xFC, 201 | 0x9D, 0x00, 0x00, 0x00, 0x00, 0x33, 0xFF, 0x57, 0x33, 0xFF, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x57, 202 | 0xFF, 0x30, 0x59, 0xFF, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x3D, 0x00, 0x00, 0x00, 203 | 0x00, 0x00, 0x02, 0x6E, 0xC9, 0x44, 0x06, 0x00, 0x00, 0x68, 0xEE, 0xFF, 0xFF, 0xFF, 0xFC, 0x85, 204 | 0x30, 0xFF, 0xFF, 0xC3, 0xDF, 0xA1, 0xD8, 0x5F, 0x55, 0xFF, 0xF0, 0x48, 0xB4, 0x00, 0x00, 0x00, 205 | 0x17, 0xF2, 0xFF, 0xEB, 0xC9, 0x0C, 0x00, 0x00, 0x00, 0x33, 0xD0, 0xFF, 0xFF, 0xF6, 0x8C, 0x08, 206 | 0x00, 0x00, 0x00, 0x6A, 0xF0, 0xFF, 0xFF, 0xAC, 0x02, 0x00, 0x00, 0x44, 0xB4, 0x5B, 0xFF, 0xF8, 207 | 0x50, 0xCE, 0x85, 0x83, 0xCE, 0xAE, 0xFF, 0xD6, 0x41, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0xE5, 0x35, 208 | 0x00, 0x0C, 0x4A, 0x83, 0xCE, 0x44, 0x02, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xA7, 0x00, 0x00, 0x00, 209 | 0x00, 0x5D, 0xC9, 0xC7, 0x57, 0x00, 0x00, 0x00, 0x3F, 0xAE, 0x41, 0x00, 0x00, 0x2A, 0xFF, 0xE3, 210 | 0xE3, 0xFF, 0x28, 0x00, 0x00, 0xD2, 0xDF, 0x06, 0x00, 0x00, 0x6A, 0xFF, 0x5D, 0x5B, 0xFF, 0x70, 211 | 0x00, 0x61, 0xFF, 0x59, 0x00, 0x00, 0x00, 0x94, 0xFF, 0x50, 0x50, 0xFF, 0x94, 0x06, 0xE5, 0xCE, 212 | 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0x5B, 0x5B, 0xFF, 0x70, 0x7F, 0xFF, 0x3B, 0x63, 0x9B, 0x59, 213 | 0x00, 0x24, 0xFC, 0xE1, 0xE1, 0xFF, 0x41, 0xF4, 0xB0, 0x83, 0xFF, 0xFA, 0xFF, 0x7B, 0x00, 0x52, 214 | 0xC7, 0xC7, 0x5D, 0x96, 0xFC, 0x24, 0xE5, 0xF6, 0x08, 0xF6, 0xE7, 0x00, 0x00, 0x00, 0x00, 0x2A, 215 | 0xFF, 0x90, 0x00, 0xFF, 0xC5, 0x00, 0xBB, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xB2, 0xF2, 0x13, 0x00, 216 | 0xFA, 0xDA, 0x00, 0xD0, 0xFC, 0x00, 0x00, 0x00, 0x41, 0xFF, 0x77, 0x00, 0x00, 0xBD, 0xFC, 0x72, 217 | 0xFC, 0xC3, 0x00, 0x00, 0x00, 0xD2, 0xE1, 0x06, 0x00, 0x00, 0x2E, 0xE7, 0xFF, 0xE9, 0x35, 0x00, 218 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x3D, 0xAC, 219 | 0xE1, 0xBB, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0xFA, 0xFF, 0xF0, 0xFC, 0xFF, 0x66, 220 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xA7, 0x00, 0x70, 0xFF, 0xA3, 0x00, 0x00, 0x00, 0x00, 221 | 0x00, 0x55, 0xFF, 0xC7, 0x0C, 0xBF, 0xFF, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xD4, 0xFF, 222 | 0xF4, 0xFF, 0xB2, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xD6, 0xFF, 0xFF, 0xCC, 0x00, 0x00, 223 | 0x72, 0xB4, 0x77, 0x00, 0x0C, 0xE9, 0xFF, 0xB4, 0xE5, 0xFF, 0x94, 0x02, 0xE5, 0xFF, 0x63, 0x00, 224 | 0x52, 0xFF, 0xFC, 0x08, 0x1D, 0xE5, 0xFF, 0xD4, 0xFF, 0xDF, 0x02, 0x00, 0x57, 0xFF, 0xFC, 0x1F, 225 | 0x00, 0x1D, 0xE7, 0xFF, 0xFF, 0x48, 0x00, 0x00, 0x15, 0xEE, 0xFF, 0xF4, 0xB0, 0xD4, 0xFF, 0xFF, 226 | 0xFF, 0xAC, 0x00, 0x00, 0x00, 0x35, 0xCC, 0xFF, 0xFF, 0xFF, 0xC1, 0x57, 0xE1, 0xFF, 0xB0, 0x04, 227 | 0x00, 0x00, 0x00, 0x06, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xAC, 0xAC, 0xF4, 0xF0, 228 | 0xDA, 0xDA, 0xB4, 0xB2, 0x0C, 0x0C, 0x00, 0x00, 0x4C, 0xAE, 0x46, 0x00, 0x13, 0xEB, 0xDA, 0x02, 229 | 0x00, 0x81, 0xFF, 0x68, 0x00, 0x00, 0xE3, 0xFC, 0x0C, 0x00, 0x22, 0xFF, 0xC3, 0x00, 0x00, 0x50, 230 | 0xFF, 0xA1, 0x00, 0x00, 0x59, 0xFF, 0x99, 0x00, 0x00, 0x59, 0xFF, 0x99, 0x00, 0x00, 0x4A, 0xFF, 231 | 0xA7, 0x00, 0x00, 0x11, 0xFF, 0xDA, 0x00, 0x00, 0x00, 0xCC, 0xFF, 0x22, 0x00, 0x00, 0x63, 0xFF, 232 | 0x81, 0x00, 0x00, 0x00, 0xCC, 0xF4, 0x17, 0x00, 0x00, 0x1B, 0x57, 0x28, 0x33, 0xAE, 0x5B, 0x00, 233 | 0x00, 0x00, 0xCE, 0xF8, 0x22, 0x00, 0x00, 0x52, 0xFF, 0x96, 0x00, 0x00, 0x02, 0xF6, 0xF4, 0x02, 234 | 0x00, 0x00, 0xAE, 0xFF, 0x3D, 0x00, 0x00, 0x96, 0xFF, 0x59, 0x00, 0x00, 0x66, 0xFF, 0x63, 0x00, 235 | 0x00, 0x70, 0xFF, 0x61, 0x00, 0x00, 0x9D, 0xFF, 0x57, 0x00, 0x00, 0xC1, 0xFF, 0x2C, 0x00, 0x11, 236 | 0xFC, 0xE1, 0x00, 0x00, 0x74, 0xFF, 0x79, 0x00, 0x0C, 0xE7, 0xDD, 0x08, 0x00, 0x1F, 0x57, 0x22, 237 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6, 0xF2, 0x00, 238 | 0x00, 0x00, 0x1B, 0x3F, 0x02, 0xA7, 0xCE, 0x00, 0x2C, 0x2E, 0x5B, 0xFF, 0xF4, 0xE1, 0xE5, 0xE9, 239 | 0xFF, 0x9B, 0x2A, 0x61, 0xA1, 0xFF, 0xFF, 0xBB, 0x63, 0x3B, 0x00, 0x00, 0xBB, 0xEB, 0xD8, 0xDA, 240 | 0x0C, 0x00, 0x00, 0x6A, 0xFF, 0x81, 0x5B, 0xFF, 0x8E, 0x00, 0x00, 0x00, 0x50, 0x17, 0x00, 0x6A, 241 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xF2, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0xFF, 0x06, 242 | 0x00, 0x00, 0x02, 0x06, 0x06, 0x96, 0xFF, 0x0C, 0x06, 0x06, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 243 | 0xFF, 0xF2, 0x33, 0x94, 0x94, 0xD4, 0xFF, 0x96, 0x94, 0x8C, 0x00, 0x00, 0x00, 0x94, 0xFF, 0x06, 244 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0xFF, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x50, 0x02, 245 | 0x00, 0x00, 0x00, 0x99, 0xAE, 0x33, 0x02, 0xFC, 0xFA, 0x06, 0x39, 0xFF, 0xA1, 0x00, 0x5F, 0xF2, 246 | 0x39, 0x00, 0x8C, 0xF2, 0xF2, 0xF2, 0x52, 0x88, 0xEB, 0xEB, 0xEB, 0x50, 0x00, 0x26, 0x39, 0x00, 247 | 0x06, 0xF6, 0xFF, 0x4C, 0x06, 0xEE, 0xFF, 0x3F, 0x00, 0x04, 0x17, 0x00, 0x00, 0x00, 0x00, 0x04, 248 | 0xAC, 0xAA, 0x00, 0x00, 0x00, 0x55, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0x46, 0x00, 0x00, 249 | 0x17, 0xFC, 0xE5, 0x00, 0x00, 0x00, 0x74, 0xFF, 0x88, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0x28, 0x00, 250 | 0x00, 0x30, 0xFF, 0xC9, 0x00, 0x00, 0x00, 0x90, 0xFF, 0x68, 0x00, 0x00, 0x02, 0xEB, 0xFA, 0x11, 251 | 0x00, 0x00, 0x4E, 0xFF, 0xAE, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 252 | 0x72, 0xC5, 0xE3, 0xA5, 0x26, 0x00, 0x00, 0x83, 0xFF, 0xFF, 0xFA, 0xFF, 0xE9, 0x17, 0x04, 0xFA, 253 | 0xFF, 0x50, 0x02, 0xC3, 0xFF, 0x88, 0x4A, 0xFF, 0xF6, 0x00, 0x00, 0x59, 0xFF, 0xD0, 0x5B, 0xFF, 254 | 0xD4, 0x00, 0x00, 0x48, 0xFF, 0xF8, 0x63, 0xFF, 0xB4, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x5D, 0xFF, 255 | 0xC5, 0x00, 0x00, 0x46, 0xFF, 0xFC, 0x50, 0xFF, 0xF2, 0x00, 0x00, 0x52, 0xFF, 0xE7, 0x0E, 0xFC, 256 | 0xFF, 0x26, 0x00, 0x96, 0xFF, 0xA7, 0x00, 0x9F, 0xFF, 0xE3, 0xB4, 0xFF, 0xFF, 0x3F, 0x00, 0x0E, 257 | 0xAE, 0xFF, 0xFF, 0xF2, 0x68, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x00, 258 | 0x19, 0xA3, 0xAE, 0x2A, 0x00, 0x3F, 0xE9, 0xFF, 0xFF, 0x3D, 0x6E, 0xFC, 0xEE, 0xFF, 0xFF, 0x3D, 259 | 0xB4, 0xE1, 0x2E, 0xFF, 0xFF, 0x3D, 0x11, 0x15, 0x06, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 0x06, 0xFF, 260 | 0xFF, 0x3D, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 261 | 0x06, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0x3D, 0x00, 0x00, 0x06, 0xFF, 0xFF, 0x3D, 262 | 0x00, 0x17, 0x85, 0xBF, 0xE1, 0xAC, 0x41, 0x00, 0x00, 0x28, 0xE9, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 263 | 0x4A, 0x00, 0x00, 0xB4, 0x99, 0x1F, 0x06, 0xBB, 0xFF, 0xAC, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 264 | 0x72, 0xFF, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xFF, 0x79, 0x00, 0x00, 0x00, 0x00, 265 | 0x00, 0x8E, 0xFF, 0xDA, 0x08, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xE5, 0x1D, 0x00, 0x00, 0x00, 266 | 0x00, 0x7B, 0xFF, 0xDF, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x77, 0xFF, 0xD2, 0x15, 0x00, 0x00, 0x00, 267 | 0x00, 0x4A, 0xFF, 0xFF, 0xF6, 0xF2, 0xF2, 0xF2, 0xF2, 0x06, 0x5D, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 268 | 0xFF, 0xFF, 0x06, 0x00, 0x4E, 0xA7, 0xD2, 0xD6, 0xAC, 0x4A, 0x00, 0x2E, 0xFC, 0xFF, 0xFC, 0xFC, 269 | 0xFF, 0xFF, 0x50, 0x00, 0x66, 0x46, 0x02, 0x06, 0xC7, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0x00, 0x00, 270 | 0xA1, 0xFF, 0x8A, 0x00, 0x00, 0x3D, 0x59, 0x8A, 0xFA, 0xCE, 0x13, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 271 | 0xE1, 0x41, 0x00, 0x00, 0x00, 0x3D, 0x59, 0x7D, 0xE9, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 272 | 0x6A, 0xFF, 0xF4, 0x19, 0x02, 0x00, 0x00, 0x00, 0x8E, 0xFF, 0xE9, 0x5D, 0xF6, 0xAE, 0xA7, 0xC3, 273 | 0xFF, 0xFF, 0x81, 0x3D, 0xDD, 0xFF, 0xFF, 0xFF, 0xE7, 0x7B, 0x00, 0x00, 0x00, 0x06, 0x0C, 0x0A, 274 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xAE, 0xAE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 275 | 0xD2, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 276 | 0x35, 0xFF, 0x8C, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x08, 0xD8, 0xDF, 0x1D, 0xFF, 0xFF, 0x00, 0x00, 277 | 0x00, 0x90, 0xFF, 0x3F, 0x3B, 0xFF, 0xFF, 0x00, 0x00, 0x3F, 0xFF, 0x94, 0x00, 0x3D, 0xFF, 0xFF, 278 | 0x00, 0x00, 0xB2, 0xFF, 0xFA, 0xF8, 0xFA, 0xFF, 0xFF, 0xF8, 0x4E, 0x7F, 0xB4, 0xB4, 0xB4, 0xC5, 279 | 0xFF, 0xFF, 0xB4, 0x39, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 280 | 0x00, 0x3D, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x74, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x0C, 0x00, 0xB2, 281 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x13, 0x00, 0xC3, 0xFF, 0x7B, 0x44, 0x44, 0x44, 0x06, 0x00, 0xEE, 282 | 0xFF, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFA, 0xFF, 0xB6, 0xDD, 0xAC, 0x48, 0x00, 0x00, 0xF4, 283 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x52, 0x00, 0x13, 0x0C, 0x00, 0x1D, 0xC9, 0xFF, 0xBD, 0x00, 0x00, 284 | 0x00, 0x00, 0x00, 0x5F, 0xFF, 0xEB, 0x17, 0x11, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xBB, 0x4A, 0xFC, 285 | 0xC3, 0xAC, 0xCE, 0xFF, 0xFF, 0x50, 0x2E, 0xD8, 0xFF, 0xFF, 0xFF, 0xE1, 0x5B, 0x00, 0x00, 0x00, 286 | 0x06, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x68, 0xAC, 0xB8, 0xB8, 0x39, 0x00, 0x00, 287 | 0x11, 0xCC, 0xFF, 0xFF, 0xFA, 0xF8, 0x50, 0x00, 0x00, 0x9F, 0xFF, 0xB4, 0x1D, 0x00, 0x00, 0x02, 288 | 0x00, 0x0E, 0xFC, 0xF2, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xAA, 0x7D, 0xE9, 0xF0, 289 | 0x94, 0x0C, 0x00, 0x5D, 0xFF, 0xF2, 0xF8, 0xBD, 0xFC, 0xFF, 0x9F, 0x00, 0x63, 0xFF, 0xF4, 0x1B, 290 | 0x00, 0x57, 0xFF, 0xF8, 0x00, 0x5B, 0xFF, 0xBF, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x06, 0x2A, 0xFF, 291 | 0xFA, 0x1D, 0x00, 0x52, 0xFF, 0xF6, 0x00, 0x00, 0xAC, 0xFF, 0xE5, 0xAA, 0xF4, 0xFF, 0x8C, 0x00, 292 | 0x00, 0x0C, 0xA1, 0xFC, 0xFF, 0xFC, 0x96, 0x06, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x02, 0x00, 293 | 0x00, 0x00, 0x6A, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x04, 0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 294 | 0xFF, 0xFF, 0xFF, 0x04, 0x24, 0x3D, 0x3D, 0x3D, 0x3D, 0x9D, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x00, 295 | 0x00, 0x02, 0xE1, 0xFF, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5B, 0xFF, 0xD8, 0x00, 0x00, 0x00, 296 | 0x00, 0x00, 0x00, 0xCE, 0xFF, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3D, 0xFF, 0xEE, 0x08, 0x00, 297 | 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0xFF, 0xFC, 0x1F, 298 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xFC, 0xFF, 299 | 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x7F, 0xB4, 0xD4, 0xAC, 0x46, 0x00, 0x00, 0xC1, 0xFF, 300 | 0xF6, 0xD0, 0xFF, 0xFF, 0x52, 0x0A, 0xFF, 0xFC, 0x17, 0x00, 0x85, 0xFF, 0xA5, 0x04, 0xFA, 0xFF, 301 | 0x2C, 0x00, 0x96, 0xFF, 0x90, 0x00, 0x6E, 0xFF, 0xF2, 0xBF, 0xFF, 0xD4, 0x17, 0x00, 0x08, 0xC5, 302 | 0xFF, 0xFF, 0xFA, 0x39, 0x00, 0x04, 0xC7, 0xFF, 0xBD, 0x7F, 0xFC, 0xFC, 0x52, 0x55, 0xFF, 0xC5, 303 | 0x00, 0x00, 0x4A, 0xFF, 0xE9, 0x61, 0xFF, 0xB0, 0x00, 0x00, 0x1B, 0xFF, 0xFC, 0x2C, 0xFF, 0xFF, 304 | 0xA7, 0x96, 0xD6, 0xFF, 0xB2, 0x00, 0x55, 0xE1, 0xFF, 0xFF, 0xFC, 0x9F, 0x11, 0x00, 0x00, 0x00, 305 | 0x0A, 0x0C, 0x02, 0x00, 0x00, 0x00, 0x0C, 0x7F, 0xB4, 0xB6, 0x90, 0x19, 0x00, 0x00, 0xC3, 0xFF, 306 | 0xFF, 0xFF, 0xFF, 0xE9, 0x19, 0x4A, 0xFF, 0xF0, 0x28, 0x08, 0xB6, 0xFF, 0x99, 0x79, 0xFF, 0xAA, 307 | 0x00, 0x00, 0x4C, 0xFF, 0xEE, 0x6A, 0xFF, 0xBF, 0x00, 0x00, 0x55, 0xFF, 0xFF, 0x37, 0xFF, 0xFF, 308 | 0x96, 0x7D, 0xE9, 0xFF, 0xFF, 0x00, 0x83, 0xFF, 0xFF, 0xFF, 0x7B, 0xFF, 0xF2, 0x00, 0x00, 0x1B, 309 | 0x4A, 0x22, 0x4C, 0xFF, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x11, 0xCC, 0xFF, 0x5B, 0x00, 0x79, 0xA1, 310 | 0xAA, 0xF2, 0xFF, 0xBB, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 0xE9, 0x81, 0x06, 0x00, 0x00, 0x06, 0x0C, 311 | 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x41, 0x00, 0x06, 0xFA, 0xFF, 0x4E, 0x04, 0xE9, 0xFF, 312 | 0x3B, 0x00, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x39, 313 | 0x00, 0x06, 0xF6, 0xFF, 0x4C, 0x06, 0xEE, 0xFF, 0x3F, 0x00, 0x04, 0x17, 0x00, 0x00, 0x2E, 0x41, 314 | 0x00, 0x06, 0xFA, 0xFF, 0x4E, 0x04, 0xE9, 0xFF, 0x3B, 0x00, 0x04, 0x0A, 0x00, 0x00, 0x00, 0x00, 315 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xAE, 0x33, 0x02, 0xFC, 0xFA, 316 | 0x06, 0x39, 0xFF, 0xA1, 0x00, 0x5F, 0xF2, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 317 | 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0xA7, 0xF2, 0x00, 0x00, 0x00, 0x28, 0xA7, 0xFF, 0xF4, 318 | 0x85, 0x00, 0x28, 0xAA, 0xFF, 0xE9, 0x7B, 0x0E, 0x00, 0x48, 0xFF, 0xFF, 0x79, 0x06, 0x00, 0x00, 319 | 0x00, 0x1F, 0xAA, 0xFC, 0xFA, 0x9B, 0x35, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x8C, 0xF4, 0xFF, 0xCE, 320 | 0x61, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x72, 0xDD, 0xF2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 321 | 0x46, 0x33, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x8C, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 322 | 0xF2, 0x02, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x39, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 0xA7, 323 | 0x9F, 0x52, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xF2, 0xE5, 0x19, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 324 | 0x00, 0x57, 0xE3, 0x72, 0x06, 0x00, 0x00, 0x00, 0x00, 0x24, 0xC3, 0xFF, 0xE5, 0x72, 0x06, 0x00, 325 | 0x00, 0x00, 0x00, 0x35, 0xB0, 0xFF, 0xE7, 0x72, 0x06, 0x00, 0x00, 0x00, 0x00, 0x28, 0xC7, 0xFF, 326 | 0xDF, 0x00, 0x00, 0x06, 0x66, 0xCE, 0xFF, 0xE3, 0x77, 0x1B, 0x8E, 0xF2, 0xFF, 0xCE, 0x5D, 0x02, 327 | 0x00, 0x57, 0xFF, 0xAE, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 328 | 0x00, 0x26, 0x88, 0xB8, 0xE5, 0xB4, 0x5F, 0x00, 0xB8, 0xFF, 0xFC, 0xF8, 0xFF, 0xFF, 0x6A, 0x33, 329 | 0x5D, 0x04, 0x00, 0x77, 0xFF, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x79, 0xFF, 0xAC, 0x00, 0x00, 0x00, 330 | 0x74, 0xFF, 0xF2, 0x2E, 0x00, 0x00, 0x7B, 0xFF, 0xDD, 0x2C, 0x00, 0x00, 0x00, 0xEE, 0xFC, 0x15, 331 | 0x00, 0x00, 0x00, 0x00, 0x5B, 0x5B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x33, 0x00, 0x00, 0x00, 332 | 0x00, 0x22, 0xFF, 0xFF, 0x3B, 0x00, 0x00, 0x00, 0x1B, 0xF6, 0xFC, 0x33, 0x00, 0x00, 0x00, 0x00, 333 | 0x0C, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2E, 0x7F, 0xA5, 0xA7, 0x94, 0x44, 0x00, 334 | 0x00, 0x00, 0x00, 0x00, 0x0C, 0xA5, 0xFF, 0xE5, 0xB0, 0xAA, 0xD8, 0xFF, 0xBD, 0x15, 0x00, 0x00, 335 | 0x02, 0xC3, 0xF2, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x52, 0xF4, 0xC3, 0x00, 0x00, 0x72, 0xFF, 0x4A, 336 | 0x15, 0x96, 0xE5, 0xEE, 0xBD, 0x6A, 0x6E, 0xFF, 0x4C, 0x00, 0xE3, 0xBF, 0x02, 0xD4, 0xF2, 0x7D, 337 | 0x5F, 0xFF, 0xA3, 0x04, 0xFC, 0x9F, 0x1B, 0xFF, 0x74, 0x55, 0xFF, 0x66, 0x00, 0x06, 0xFF, 0x9D, 338 | 0x00, 0xF2, 0xA7, 0x48, 0xFF, 0x57, 0x63, 0xFF, 0x44, 0x00, 0x22, 0xFF, 0x96, 0x00, 0xF8, 0xA1, 339 | 0x39, 0xFF, 0x5F, 0x55, 0xFF, 0x6C, 0x00, 0x7B, 0xFF, 0x9D, 0x35, 0xFF, 0x5B, 0x04, 0xFA, 0xA7, 340 | 0x02, 0xC3, 0xFF, 0xF2, 0xF8, 0xBB, 0xFF, 0xFA, 0xC5, 0x02, 0x00, 0x9D, 0xFC, 0x39, 0x02, 0x4C, 341 | 0x5B, 0x24, 0x00, 0x52, 0x55, 0x06, 0x00, 0x00, 0x15, 0xDD, 0xF6, 0x81, 0x44, 0x0E, 0x37, 0x52, 342 | 0x9D, 0x08, 0x00, 0x00, 0x00, 0x00, 0x11, 0x92, 0xF6, 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0x08, 0x00, 343 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, 0x4A, 0x44, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 344 | 0x00, 0x3D, 0xB4, 0xB4, 0x7B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xFA, 0xF8, 345 | 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xF6, 0xFA, 0xAA, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x00, 346 | 0x00, 0x59, 0xFF, 0xB2, 0x57, 0xFF, 0xB2, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0x68, 0x0C, 347 | 0xFC, 0xFA, 0x11, 0x00, 0x00, 0x00, 0x0E, 0xFC, 0xFF, 0x1B, 0x00, 0xB8, 0xFF, 0x66, 0x00, 0x00, 348 | 0x00, 0x66, 0xFF, 0xE9, 0x63, 0x63, 0xAC, 0xFF, 0xBF, 0x00, 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0xFF, 349 | 0xFF, 0xFF, 0xFF, 0xFF, 0x1B, 0x00, 0x1D, 0xFF, 0xFF, 0x74, 0x63, 0x63, 0x63, 0xDA, 0xFF, 0x72, 350 | 0x00, 0x74, 0xFF, 0xE1, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0xCE, 0x00, 0xD0, 0xFF, 0x92, 0x00, 351 | 0x00, 0x00, 0x00, 0x3B, 0xFF, 0xFF, 0x26, 0x72, 0xAE, 0xAE, 0xAC, 0xA5, 0x90, 0x3F, 0x00, 0x00, 352 | 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8A, 0x00, 0xA7, 0xFF, 0xA7, 0x13, 0x3B, 0xAC, 0xFF, 353 | 0xF8, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x55, 0xFF, 0xF8, 0x00, 0xA7, 0xFF, 0xC5, 0x63, 0x81, 354 | 0xDD, 0xFF, 0x8E, 0x00, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD0, 0x22, 0x00, 0xA7, 0xFF, 0xBF, 355 | 0x50, 0x55, 0xB0, 0xFF, 0xE9, 0x06, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x0E, 0xFF, 0xFF, 0x48, 0xA7, 356 | 0xFF, 0xA1, 0x00, 0x00, 0x50, 0xFF, 0xFF, 0x37, 0xA7, 0xFF, 0xF8, 0xEB, 0xEE, 0xFF, 0xFF, 0xC9, 357 | 0x00, 0xA7, 0xFF, 0xFF, 0xFF, 0xFC, 0xE5, 0x90, 0x11, 0x00, 0x00, 0x00, 0x00, 0x4E, 0xAC, 0xD6, 358 | 0xC9, 0xA3, 0x4A, 0x00, 0x00, 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0x00, 0x59, 0xFF, 0xFF, 359 | 0x70, 0x06, 0x0C, 0x61, 0x48, 0x00, 0xC7, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFC, 360 | 0xFF, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 361 | 0x06, 0xFF, 0xFF, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0x72, 0x00, 0x00, 0x00, 362 | 0x00, 0x00, 0x00, 0x96, 0xFF, 0xE3, 0x19, 0x00, 0x00, 0x02, 0x24, 0x00, 0x1B, 0xE9, 0xFF, 0xFA, 363 | 0xB6, 0xB6, 0xF6, 0xA1, 0x00, 0x00, 0x24, 0xBB, 0xFC, 0xFF, 0xFF, 0xF2, 0x6C, 0x00, 0x00, 0x00, 364 | 0x00, 0x02, 0x0C, 0x0A, 0x00, 0x00, 0x72, 0xAE, 0xAE, 0xAC, 0xA5, 0x79, 0x24, 0x00, 0x00, 0x00, 365 | 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x7F, 0x00, 0x00, 0xA7, 0xFF, 0xA7, 0x13, 0x39, 0x85, 366 | 0xFF, 0xFF, 0x6A, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x8A, 0xFF, 0xE5, 0x00, 0xA7, 0xFF, 367 | 0xA1, 0x00, 0x00, 0x00, 0x3B, 0xFF, 0xFF, 0x22, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 368 | 0xFF, 0x44, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x30, 0xFF, 0xFF, 0x2A, 0xA7, 0xFF, 0xA1, 0x00, 369 | 0x00, 0x00, 0x77, 0xFF, 0xF4, 0x02, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x52, 0xF2, 0xFF, 0x88, 0x00, 370 | 0xA7, 0xFF, 0xF8, 0xEB, 0xF8, 0xFF, 0xFF, 0xBB, 0x08, 0x00, 0xA7, 0xFF, 0xFF, 0xFC, 0xF2, 0xB6, 371 | 0x5F, 0x00, 0x00, 0x00, 0x72, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x66, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 372 | 0xFF, 0x94, 0xA7, 0xFF, 0xA7, 0x13, 0x13, 0x13, 0x0C, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 373 | 0xA7, 0xFF, 0xD8, 0x94, 0x94, 0x94, 0x2A, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x4A, 0xA7, 0xFF, 374 | 0xC1, 0x57, 0x57, 0x57, 0x19, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 375 | 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xF8, 0xEB, 0xEB, 0xEB, 0x88, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 376 | 0x94, 0x72, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x44, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x63, 0xA7, 377 | 0xFF, 0xA1, 0x13, 0x13, 0x13, 0x06, 0xA7, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x9D, 378 | 0x06, 0x06, 0x06, 0x02, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x44, 0xA7, 0xFF, 0xE1, 0xB4, 0xB4, 379 | 0xB4, 0x30, 0xA7, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x00, 380 | 0xA7, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 381 | 0x00, 0x30, 0x99, 0xBD, 0xE5, 0xB6, 0x94, 0x33, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 382 | 0xFF, 0x55, 0x00, 0x52, 0xFF, 0xFF, 0xA7, 0x24, 0x00, 0x1B, 0x6A, 0x02, 0x00, 0xC7, 0xFF, 0xC3, 383 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xFC, 0xFF, 0x52, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 384 | 0x0C, 0xFF, 0xFF, 0x44, 0x00, 0x9B, 0xFF, 0xFF, 0xFF, 0xA7, 0x06, 0xFF, 0xFF, 0x48, 0x00, 0x8E, 385 | 0xEB, 0xF8, 0xFF, 0xA7, 0x00, 0xEB, 0xFF, 0x79, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xA7, 0x00, 0x94, 386 | 0xFF, 0xEB, 0x2A, 0x00, 0x00, 0x9B, 0xFF, 0xA7, 0x00, 0x17, 0xE5, 0xFF, 0xFC, 0xBB, 0xB2, 0xF2, 387 | 0xFF, 0xA7, 0x00, 0x00, 0x1D, 0xAA, 0xFC, 0xFF, 0xFF, 0xFF, 0xD6, 0x6A, 0x00, 0x00, 0x00, 0x00, 388 | 0x02, 0x0C, 0x0C, 0x02, 0x00, 0x00, 0x72, 0xAE, 0x6E, 0x00, 0x00, 0x00, 0x08, 0xAE, 0xAE, 0x08, 389 | 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 390 | 0x0C, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 391 | 0xD8, 0x94, 0x94, 0x94, 0x99, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 392 | 0xFF, 0x0C, 0xA7, 0xFF, 0xC1, 0x57, 0x57, 0x57, 0x5F, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 0xA1, 0x00, 393 | 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x0C, 394 | 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xFF, 0x0C, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 395 | 0x0C, 0xFF, 0xFF, 0x0C, 0x66, 0xAE, 0xAE, 0xAE, 0xAE, 0x3B, 0x61, 0xF8, 0xFF, 0xFF, 0xE9, 0x39, 396 | 0x00, 0x48, 0xFF, 0xFF, 0x06, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 397 | 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x44, 398 | 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x00, 0x00, 0x35, 0xC3, 0xFF, 0xFF, 0xAA, 0x1D, 399 | 0x94, 0xFF, 0xFF, 0xFF, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x72, 0xAE, 0x6E, 0x00, 0x00, 0x00, 0xA7, 400 | 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 401 | 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 402 | 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 403 | 0xFF, 0xA1, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0xD2, 0xFF, 0x7D, 0x0C, 0xBF, 404 | 0xD6, 0xFF, 0xFF, 0x2C, 0x0C, 0xFF, 0xFF, 0xEB, 0x5F, 0x00, 0x00, 0x08, 0x0C, 0x00, 0x00, 0x00, 405 | 0x72, 0xAE, 0x6E, 0x00, 0x00, 0x00, 0x94, 0xAE, 0x6E, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x85, 0xFF, 406 | 0xE3, 0x15, 0xA7, 0xFF, 0xA1, 0x00, 0x50, 0xFF, 0xF8, 0x33, 0x00, 0xA7, 0xFF, 0xA1, 0x28, 0xF2, 407 | 0xFF, 0x5F, 0x00, 0x00, 0xA7, 0xFF, 0xAA, 0xD6, 0xFF, 0x96, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFC, 408 | 0xFF, 0xFF, 0x77, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xB6, 0xFF, 0xF8, 0x22, 0x00, 0x00, 0xA7, 409 | 0xFF, 0xA1, 0x00, 0xC5, 0xFF, 0xBB, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x2A, 0xFC, 0xFF, 0x59, 410 | 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x85, 0xFF, 0xE9, 0x13, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x08, 411 | 0xDF, 0xFF, 0x9B, 0x72, 0xAE, 0x6E, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 412 | 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0xA7, 413 | 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 414 | 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 415 | 0x00, 0x00, 0xA7, 0xFF, 0xF8, 0xEB, 0xEB, 0xEB, 0xE5, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 416 | 0x72, 0xAE, 0xAE, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x41, 0xAE, 0xAE, 0x7B, 0xA7, 0xFF, 0xFF, 0xCC, 417 | 0x00, 0x00, 0x00, 0x00, 0xAE, 0xFF, 0xFF, 0xB4, 0xA7, 0xFF, 0xF6, 0xFF, 0x1B, 0x00, 0x00, 0x04, 418 | 0xFA, 0xF6, 0xFF, 0xB4, 0xA7, 0xFF, 0xB2, 0xFF, 0x68, 0x00, 0x00, 0x57, 0xFF, 0xB2, 0xFF, 0xB4, 419 | 0xA7, 0xFF, 0x6A, 0xFF, 0xB6, 0x00, 0x00, 0xA7, 0xFC, 0x63, 0xFF, 0xB4, 0xA7, 0xFF, 0x57, 0xCC, 420 | 0xFA, 0x08, 0x04, 0xFA, 0xC1, 0x50, 0xFF, 0xB4, 0xA7, 0xFF, 0x57, 0x7F, 0xFF, 0x57, 0x52, 0xFF, 421 | 0x6A, 0x50, 0xFF, 0xB4, 0xA7, 0xFF, 0x57, 0x2C, 0xFF, 0xA7, 0xA5, 0xFF, 0x1B, 0x50, 0xFF, 0xB4, 422 | 0xA7, 0xFF, 0x57, 0x00, 0xDF, 0xF6, 0xF6, 0xC3, 0x00, 0x50, 0xFF, 0xB4, 0xA7, 0xFF, 0x57, 0x00, 423 | 0x90, 0xFF, 0xFF, 0x72, 0x00, 0x50, 0xFF, 0xB4, 0xA7, 0xFF, 0x57, 0x00, 0x41, 0xFF, 0xFF, 0x1D, 424 | 0x00, 0x50, 0xFF, 0xB4, 0x72, 0xAE, 0xAE, 0x48, 0x00, 0x00, 0x00, 0x2A, 0xAE, 0x9D, 0xA7, 0xFF, 425 | 0xFF, 0xDD, 0x06, 0x00, 0x00, 0x3D, 0xFF, 0xE5, 0xA7, 0xFF, 0xFF, 0xFF, 0x77, 0x00, 0x00, 0x3D, 426 | 0xFF, 0xE5, 0xA7, 0xFF, 0xA1, 0xFF, 0xF4, 0x19, 0x00, 0x3D, 0xFF, 0xE5, 0xA7, 0xFF, 0x4C, 0xC3, 427 | 0xFF, 0x9F, 0x00, 0x3D, 0xFF, 0xE5, 0xA7, 0xFF, 0x52, 0x30, 0xFF, 0xFF, 0x33, 0x3D, 0xFF, 0xE5, 428 | 0xA7, 0xFF, 0x57, 0x00, 0x99, 0xFF, 0xCE, 0x1D, 0xFF, 0xE5, 0xA7, 0xFF, 0x57, 0x00, 0x15, 0xF2, 429 | 0xFF, 0x6A, 0xFF, 0xE5, 0xA7, 0xFF, 0x57, 0x00, 0x00, 0x77, 0xFF, 0xEB, 0xFF, 0xE5, 0xA7, 0xFF, 430 | 0x57, 0x00, 0x00, 0x02, 0xDA, 0xFF, 0xFF, 0xE5, 0xA7, 0xFF, 0x57, 0x00, 0x00, 0x00, 0x4C, 0xFF, 431 | 0xFF, 0xE5, 0x00, 0x00, 0x00, 0x5F, 0xAC, 0xE3, 0xE1, 0xAC, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 432 | 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA7, 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0x6A, 0x04, 433 | 0x04, 0x72, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0xD6, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFF, 434 | 0xC1, 0x00, 0x02, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xF8, 0x00, 0x0C, 0xFF, 435 | 0xFF, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xFF, 0x02, 0x06, 0xFF, 0xFF, 0x4C, 0x00, 0x00, 436 | 0x00, 0x00, 0x57, 0xFF, 0xFC, 0x00, 0x00, 0xE9, 0xFF, 0x83, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 437 | 0xD4, 0x00, 0x00, 0x8C, 0xFF, 0xEE, 0x28, 0x00, 0x00, 0x2C, 0xF2, 0xFF, 0x7D, 0x00, 0x00, 0x11, 438 | 0xDF, 0xFF, 0xFC, 0xBF, 0xC3, 0xFC, 0xFF, 0xD6, 0x0C, 0x00, 0x00, 0x00, 0x15, 0xA1, 0xFA, 0xFF, 439 | 0xFF, 0xFA, 0x96, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 0x0A, 0x00, 0x00, 0x00, 440 | 0x00, 0x00, 0x72, 0xAE, 0xAE, 0xAC, 0xA5, 0x7B, 0x15, 0x00, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 441 | 0xE9, 0x1D, 0xA7, 0xFF, 0xA7, 0x13, 0x50, 0xE7, 0xFF, 0x90, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x9D, 442 | 0xFF, 0xAE, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0xC3, 0xFF, 0xA5, 0xA7, 0xFF, 0xDF, 0xAA, 0xDA, 0xFF, 443 | 0xFF, 0x3F, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0x5D, 0x00, 0xA7, 0xFF, 0xB8, 0x3B, 0x0C, 0x00, 444 | 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 445 | 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5F, 0xAC, 0xE3, 446 | 0xE1, 0xAC, 0x59, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB6, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xA7, 447 | 0x00, 0x00, 0x00, 0x72, 0xFF, 0xFF, 0x6A, 0x04, 0x04, 0x72, 0xFF, 0xFF, 0x5D, 0x00, 0x00, 0xD6, 448 | 0xFF, 0x9D, 0x00, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0xC1, 0x00, 0x02, 0xFF, 0xFF, 0x50, 0x00, 0x00, 449 | 0x00, 0x00, 0x5D, 0xFF, 0xF8, 0x00, 0x0C, 0xFF, 0xFF, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x50, 0xFF, 450 | 0xFF, 0x02, 0x06, 0xFF, 0xFF, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x57, 0xFF, 0xFC, 0x00, 0x00, 0xE9, 451 | 0xFF, 0x83, 0x00, 0x00, 0x00, 0x00, 0x90, 0xFF, 0xD4, 0x00, 0x00, 0x8C, 0xFF, 0xEE, 0x28, 0x00, 452 | 0x00, 0x2C, 0xF2, 0xFF, 0x81, 0x00, 0x00, 0x11, 0xDF, 0xFF, 0xFC, 0xBF, 0xC3, 0xFC, 0xFF, 0xD8, 453 | 0x0E, 0x00, 0x00, 0x00, 0x15, 0xA1, 0xFA, 0xFF, 0xFF, 0xFF, 0xC5, 0x15, 0x00, 0x00, 0x00, 0x00, 454 | 0x00, 0x00, 0x02, 0x0C, 0x8E, 0xFF, 0xF4, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 455 | 0x00, 0xBB, 0xFF, 0xE9, 0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x92, 0x94, 456 | 0x77, 0x00, 0x72, 0xAE, 0xAE, 0xAA, 0xA5, 0x70, 0x17, 0x00, 0x00, 0xA7, 0xFF, 0xFF, 0xFF, 0xFF, 457 | 0xFF, 0xEB, 0x26, 0x00, 0xA7, 0xFF, 0xA7, 0x1F, 0x4E, 0xE3, 0xFF, 0x9D, 0x00, 0xA7, 0xFF, 0xA1, 458 | 0x00, 0x00, 0x9B, 0xFF, 0xB0, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x1B, 0xD4, 0xFF, 0x8C, 0x00, 0xA7, 459 | 0xFF, 0xFC, 0xF8, 0xFF, 0xFF, 0xD0, 0x13, 0x00, 0xA7, 0xFF, 0xF8, 0xEE, 0xFF, 0xEE, 0x08, 0x00, 460 | 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0xC5, 0xFF, 0x85, 0x00, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x2A, 0xFC, 461 | 0xFC, 0x33, 0x00, 0xA7, 0xFF, 0xA1, 0x00, 0x00, 0x85, 0xFF, 0xD6, 0x06, 0xA7, 0xFF, 0xA1, 0x00, 462 | 0x00, 0x08, 0xDF, 0xFF, 0x8C, 0x00, 0x04, 0x79, 0xBB, 0xE5, 0xB6, 0x81, 0x24, 0x00, 0xAC, 0xFF, 463 | 0xFF, 0xFF, 0xFF, 0xFF, 0x5F, 0x11, 0xFF, 0xFF, 0x48, 0x02, 0x2A, 0x7B, 0x02, 0x28, 0xFF, 0xFF, 464 | 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xEB, 0x72, 0x08, 0x00, 0x00, 0x00, 0x2A, 0xDD, 465 | 0xFF, 0xFF, 0xE7, 0x4E, 0x00, 0x00, 0x00, 0x06, 0x70, 0xE5, 0xFF, 0xFF, 0x3B, 0x00, 0x00, 0x00, 466 | 0x00, 0x0C, 0xC9, 0xFF, 0xA1, 0x2C, 0x44, 0x00, 0x00, 0x00, 0xAC, 0xFF, 0xA1, 0x50, 0xFF, 0xEE, 467 | 0xB0, 0xC3, 0xFF, 0xFF, 0x46, 0x2E, 0xCE, 0xFF, 0xFF, 0xFF, 0xE3, 0x5D, 0x00, 0x00, 0x00, 0x02, 468 | 0x0C, 0x0A, 0x00, 0x00, 0x00, 0x77, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x3B, 0xAE, 0xFF, 469 | 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x57, 0x2A, 0x3D, 0x3D, 0xC5, 0xFF, 0x88, 0x3D, 0x3D, 0x15, 470 | 0x00, 0x00, 0x00, 0xB4, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0x63, 0x00, 471 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 472 | 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 473 | 0xB4, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 474 | 0x00, 0x00, 0xB4, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x77, 0xAE, 0x66, 0x00, 0x00, 0x00, 0x2E, 0xAE, 475 | 0xAE, 0x04, 0xAE, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x06, 0xAE, 0xFF, 0x94, 0x00, 476 | 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x06, 0xAE, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x06, 477 | 0xAE, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x06, 0xAE, 0xFF, 0x94, 0x00, 0x00, 0x00, 478 | 0x44, 0xFF, 0xFF, 0x06, 0xAE, 0xFF, 0x94, 0x00, 0x00, 0x00, 0x44, 0xFF, 0xFF, 0x06, 0xAC, 0xFF, 479 | 0x9B, 0x00, 0x00, 0x00, 0x4A, 0xFF, 0xFF, 0x02, 0x83, 0xFF, 0xDF, 0x11, 0x00, 0x00, 0x9B, 0xFF, 480 | 0xDA, 0x00, 0x19, 0xEE, 0xFF, 0xF6, 0xB4, 0xDA, 0xFF, 0xFF, 0x59, 0x00, 0x00, 0x2A, 0xC3, 0xFF, 481 | 0xFF, 0xFF, 0xDA, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, 482 | 0x96, 0xAE, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x72, 0xAE, 0x6E, 0x92, 0xFF, 0xA7, 0x00, 0x00, 0x00, 483 | 0x00, 0xE7, 0xFF, 0x52, 0x3D, 0xFF, 0xF6, 0x02, 0x00, 0x00, 0x3B, 0xFF, 0xF8, 0x04, 0x00, 0xE3, 484 | 0xFF, 0x4C, 0x00, 0x00, 0x8C, 0xFF, 0xA3, 0x00, 0x00, 0x8E, 0xFF, 0x9D, 0x00, 0x00, 0xDA, 0xFF, 485 | 0x4E, 0x00, 0x00, 0x39, 0xFF, 0xE9, 0x00, 0x2C, 0xFF, 0xF2, 0x02, 0x00, 0x00, 0x00, 0xE1, 0xFF, 486 | 0x3D, 0x7F, 0xFF, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x8C, 0xFF, 0x83, 0xC3, 0xFF, 0x48, 0x00, 0x00, 487 | 0x00, 0x00, 0x35, 0xFF, 0xC1, 0xFC, 0xEE, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDD, 0xFF, 0xFF, 488 | 0x9B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x85, 0xFF, 0xFF, 0x41, 0x00, 0x00, 0x00, 0xA1, 0xAE, 489 | 0x37, 0x00, 0x00, 0x00, 0x9F, 0xAE, 0x3F, 0x00, 0x00, 0x00, 0x8A, 0xAE, 0x3F, 0xAC, 0xFF, 0x7F, 490 | 0x00, 0x00, 0x19, 0xFF, 0xFF, 0xA1, 0x00, 0x00, 0x02, 0xFA, 0xFF, 0x2E, 0x6A, 0xFF, 0xB4, 0x00, 491 | 0x00, 0x57, 0xFF, 0xFF, 0xE3, 0x00, 0x00, 0x3F, 0xFF, 0xF2, 0x00, 0x28, 0xFF, 0xF6, 0x00, 0x00, 492 | 0xA5, 0xFF, 0xC7, 0xFF, 0x24, 0x00, 0x74, 0xFF, 0xA7, 0x00, 0x00, 0xEB, 0xFF, 0x35, 0x00, 0xE7, 493 | 0xF4, 0x6A, 0xFF, 0x66, 0x00, 0xAC, 0xFF, 0x68, 0x00, 0x00, 0xA7, 0xFF, 0x6A, 0x26, 0xFF, 0xAC, 494 | 0x3B, 0xFF, 0xA7, 0x00, 0xF4, 0xFF, 0x2A, 0x00, 0x00, 0x63, 0xFF, 0xA7, 0x63, 0xFF, 0x70, 0x02, 495 | 0xF6, 0xEE, 0x28, 0xFF, 0xEB, 0x00, 0x00, 0x00, 0x26, 0xFF, 0xE3, 0xA3, 0xFF, 0x33, 0x00, 0xAE, 496 | 0xFF, 0x79, 0xFF, 0xA7, 0x00, 0x00, 0x00, 0x00, 0xE7, 0xFF, 0xCE, 0xF6, 0x00, 0x00, 0x6E, 0xFF, 497 | 0xE5, 0xFF, 0x63, 0x00, 0x00, 0x00, 0x00, 0xA5, 0xFF, 0xFF, 0xAA, 0x00, 0x00, 0x30, 0xFF, 0xFF, 498 | 0xFF, 0x28, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xFF, 0x68, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0xE7, 499 | 0x00, 0x00, 0x00, 0x5B, 0xAE, 0x9D, 0x00, 0x00, 0x00, 0x00, 0x99, 0xAE, 0x59, 0x13, 0xE9, 0xFF, 500 | 0x72, 0x00, 0x00, 0x61, 0xFF, 0xE7, 0x0E, 0x00, 0x57, 0xFF, 0xF0, 0x17, 0x0C, 0xE7, 0xFF, 0x52, 501 | 0x00, 0x00, 0x00, 0xB2, 0xFF, 0xA5, 0x8A, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x1B, 0xF2, 0xFF, 502 | 0xFF, 0xF4, 0x1B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0xFF, 0xFF, 0x85, 0x00, 0x00, 0x00, 0x00, 503 | 0x00, 0x1D, 0xF2, 0xFF, 0xFF, 0xE7, 0x13, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0x90, 0xBD, 0xFF, 504 | 0xA5, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xE5, 0x0C, 0x22, 0xF8, 0xFF, 0x50, 0x00, 0x15, 0xEB, 0xFF, 505 | 0x57, 0x00, 0x00, 0x7F, 0xFF, 0xE9, 0x13, 0xA7, 0xFF, 0xB8, 0x00, 0x00, 0x00, 0x06, 0xDD, 0xFF, 506 | 0xA5, 0x8A, 0xAE, 0x6A, 0x00, 0x00, 0x00, 0x2A, 0xAE, 0xAE, 0x19, 0x55, 0xFF, 0xF6, 0x15, 0x00, 507 | 0x00, 0xAC, 0xFF, 0xB0, 0x00, 0x00, 0xCE, 0xFF, 0x88, 0x00, 0x2C, 0xFF, 0xFC, 0x28, 0x00, 0x00, 508 | 0x3D, 0xFF, 0xF8, 0x19, 0xAE, 0xFF, 0x96, 0x00, 0x00, 0x00, 0x00, 0xB8, 0xFF, 0xB8, 0xFF, 0xF6, 509 | 0x19, 0x00, 0x00, 0x00, 0x00, 0x2C, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 510 | 0xA1, 0xFF, 0xEB, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xBB, 0x00, 0x00, 0x00, 511 | 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 512 | 0xBB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xBB, 0x00, 0x00, 0x00, 0x00, 0x48, 513 | 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0xAE, 0x2A, 0x6A, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 514 | 0x33, 0x1B, 0x3D, 0x3D, 0x3D, 0x3D, 0xDD, 0xFF, 0xA7, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6C, 0xFF, 515 | 0xE5, 0x13, 0x00, 0x00, 0x00, 0x00, 0x26, 0xF6, 0xFF, 0x44, 0x00, 0x00, 0x00, 0x00, 0x02, 0xCE, 516 | 0xFF, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0xFF, 0xD6, 0x08, 0x00, 0x00, 0x00, 0x00, 0x37, 517 | 0xFF, 0xFC, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xDD, 0xFF, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 518 | 0x88, 0xFF, 0xFF, 0xEB, 0xEB, 0xEB, 0xEB, 0xEB, 0x4A, 0xA1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 519 | 0xFF, 0x50, 0xAA, 0xAE, 0xAE, 0x6A, 0xF8, 0xFC, 0xBB, 0x72, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 520 | 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 521 | 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xF8, 0x00, 0x00, 0xF8, 0xFA, 522 | 0x3D, 0x24, 0xF8, 0xFF, 0xFF, 0x9B, 0x55, 0x57, 0x57, 0x35, 0x81, 0xAE, 0x26, 0x00, 0x00, 0x00, 523 | 0x70, 0xFF, 0x88, 0x00, 0x00, 0x00, 0x15, 0xFC, 0xE5, 0x00, 0x00, 0x00, 0x00, 0xB2, 0xFF, 0x46, 524 | 0x00, 0x00, 0x00, 0x52, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0x02, 0xEE, 0xFA, 0x0C, 0x00, 0x00, 0x00, 525 | 0x92, 0xFF, 0x66, 0x00, 0x00, 0x00, 0x37, 0xFF, 0xC7, 0x00, 0x00, 0x00, 0x00, 0xD4, 0xFF, 0x26, 526 | 0x00, 0x00, 0x00, 0x79, 0xFF, 0x85, 0x00, 0x00, 0x00, 0x19, 0xFC, 0xE3, 0x6E, 0xAE, 0xAE, 0xA5, 527 | 0x77, 0xBB, 0xFC, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 528 | 0x00, 0x00, 0xF8, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 529 | 0x00, 0x00, 0xF8, 0xF2, 0x00, 0x00, 0xF8, 0xF2, 0x26, 0x3D, 0xFA, 0xF2, 0xA1, 0xFF, 0xFF, 0xF2, 530 | 0x37, 0x57, 0x57, 0x52, 0x00, 0x00, 0x00, 0x6E, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 531 | 0xF2, 0xFF, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0xFC, 0xDD, 0xC7, 0x00, 0x00, 0x00, 0x00, 532 | 0x04, 0xE5, 0xAA, 0x59, 0xFF, 0x48, 0x00, 0x00, 0x00, 0x68, 0xFF, 0x35, 0x00, 0xD8, 0xD2, 0x00, 533 | 0x00, 0x00, 0xD8, 0xC9, 0x00, 0x00, 0x59, 0xFF, 0x55, 0x00, 0x50, 0xFF, 0x5D, 0x00, 0x00, 0x00, 534 | 0xD8, 0xD8, 0x00, 0x28, 0x3D, 0x02, 0x00, 0x00, 0x00, 0x22, 0x3D, 0x0C, 0xA7, 0xA7, 0xA7, 0xA7, 535 | 0xA7, 0xA7, 0x28, 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x15, 0x17, 0x63, 0x63, 0x1B, 0x00, 0x00, 536 | 0x72, 0xFF, 0xC7, 0x04, 0x00, 0x00, 0x41, 0xE1, 0x88, 0x00, 0x00, 0x08, 0x4A, 0x50, 0x4A, 0x04, 537 | 0x00, 0x00, 0x55, 0xFA, 0xFF, 0xFF, 0xFF, 0xE9, 0x39, 0x00, 0x15, 0xB8, 0x63, 0x50, 0xB6, 0xFF, 538 | 0xC3, 0x00, 0x00, 0x00, 0x00, 0x04, 0x50, 0xFF, 0xF2, 0x00, 0x4A, 0xC3, 0xFA, 0xFF, 0xFF, 0xFF, 539 | 0xF2, 0x26, 0xFF, 0xFF, 0x8A, 0x4A, 0x7B, 0xFF, 0xF2, 0x57, 0xFF, 0xF6, 0x00, 0x00, 0x68, 0xFF, 540 | 0xF2, 0x3B, 0xFF, 0xFF, 0x9B, 0x90, 0xF2, 0xFF, 0xF2, 0x00, 0x92, 0xFF, 0xFF, 0xE9, 0x4A, 0xCE, 541 | 0xF2, 0x00, 0x00, 0x06, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x44, 0x5D, 0x22, 0x00, 0x00, 0x00, 0x00, 542 | 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 543 | 0x00, 0xBB, 0xFF, 0x59, 0x19, 0x4E, 0x39, 0x00, 0x00, 0xBB, 0xFF, 0x9B, 0xF4, 0xFF, 0xFF, 0xB0, 544 | 0x00, 0xBB, 0xFF, 0xF6, 0x7F, 0x88, 0xFF, 0xFF, 0x63, 0xBB, 0xFF, 0x90, 0x00, 0x00, 0xAC, 0xFF, 545 | 0xAA, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 0xB6, 0xBB, 0xFF, 0x5F, 0x00, 0x00, 0x77, 0xFF, 546 | 0xB4, 0xBB, 0xFF, 0xB2, 0x00, 0x00, 0xC5, 0xFF, 0xA3, 0xBB, 0xFF, 0xFF, 0xBF, 0xC5, 0xFF, 0xFF, 547 | 0x44, 0xBB, 0xFA, 0x4E, 0xE5, 0xFF, 0xFA, 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x04, 0x00, 548 | 0x00, 0x00, 0x00, 0x00, 0x33, 0x50, 0x4C, 0x11, 0x00, 0x00, 0x19, 0xCE, 0xFF, 0xFF, 0xFF, 0xFC, 549 | 0x13, 0x00, 0xC9, 0xFF, 0xDA, 0x6E, 0x92, 0x9D, 0x00, 0x26, 0xFF, 0xFF, 0x2A, 0x00, 0x00, 0x00, 550 | 0x00, 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0x00, 551 | 0x00, 0x24, 0xFF, 0xFF, 0x48, 0x00, 0x00, 0x1B, 0x00, 0x00, 0xC3, 0xFF, 0xF4, 0xAC, 0xB4, 0xF6, 552 | 0x00, 0x00, 0x17, 0xB2, 0xFC, 0xFF, 0xFF, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x04, 0x0C, 0x06, 0x00, 553 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x59, 0x5D, 0x1D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 554 | 0xF2, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xFF, 0x50, 0x00, 0x00, 0x0E, 0x4E, 555 | 0x44, 0x00, 0xF0, 0xFF, 0x50, 0x00, 0x3D, 0xF0, 0xFF, 0xFF, 0xC7, 0xD2, 0xFF, 0x50, 0x00, 0xE1, 556 | 0xFF, 0xDA, 0x72, 0xC3, 0xFF, 0xFF, 0x50, 0x33, 0xFF, 0xFF, 0x2A, 0x00, 0x06, 0xF6, 0xFF, 0x50, 557 | 0x4E, 0xFF, 0xF6, 0x00, 0x00, 0x00, 0xB6, 0xFF, 0x50, 0x4E, 0xFF, 0xF8, 0x00, 0x00, 0x00, 0xBB, 558 | 0xFF, 0x50, 0x26, 0xFF, 0xFF, 0x37, 0x00, 0x0E, 0xFA, 0xFF, 0x50, 0x00, 0xCC, 0xFF, 0xEE, 0xA5, 559 | 0xD8, 0xFA, 0xFF, 0x50, 0x00, 0x1D, 0xD6, 0xFF, 0xFC, 0x85, 0x90, 0xFF, 0x50, 0x00, 0x00, 0x00, 560 | 0x0A, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x50, 0x3F, 0x02, 0x00, 0x00, 0x00, 561 | 0x19, 0xCE, 0xFF, 0xFF, 0xFF, 0xDD, 0x26, 0x00, 0x00, 0xBF, 0xFF, 0xAA, 0x4C, 0x90, 0xFF, 0xD0, 562 | 0x00, 0x24, 0xFF, 0xFF, 0x0E, 0x00, 0x00, 0xF4, 0xFF, 0x1D, 0x4E, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 563 | 0xFF, 0xFF, 0x44, 0x4C, 0xFF, 0xF8, 0x63, 0x63, 0x63, 0x63, 0x63, 0x1B, 0x1B, 0xFF, 0xFF, 0x30, 564 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA1, 0xFF, 0xE9, 0x9B, 0x96, 0xAA, 0xAE, 0x00, 0x00, 0x04, 565 | 0x8A, 0xF4, 0xFF, 0xFF, 0xFA, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x00, 0x00, 0x00, 566 | 0x00, 0x00, 0x13, 0x57, 0x63, 0x57, 0x17, 0x00, 0x17, 0xE9, 0xFF, 0xFF, 0xFF, 0x3D, 0x00, 0x5D, 567 | 0xFF, 0xF0, 0x57, 0x5B, 0x00, 0x00, 0x6C, 0xFF, 0xC5, 0x3D, 0x2A, 0x00, 0x81, 0xFF, 0xFF, 0xFF, 568 | 0xFF, 0xAE, 0x00, 0x44, 0xA1, 0xFF, 0xD2, 0x63, 0x44, 0x00, 0x00, 0x63, 0xFF, 0xB4, 0x00, 0x00, 569 | 0x00, 0x00, 0x63, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x00, 570 | 0x63, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 571 | 0xB4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x4E, 0x44, 0x00, 0x24, 0x3D, 0x13, 0x00, 0x3D, 0xF0, 572 | 0xFF, 0xFF, 0xC7, 0xB6, 0xFF, 0x50, 0x00, 0xDD, 0xFF, 0xD6, 0x74, 0xC3, 0xFF, 0xFF, 0x50, 0x30, 573 | 0xFF, 0xFF, 0x28, 0x00, 0x02, 0xF2, 0xFF, 0x50, 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0xB6, 0xFF, 574 | 0x50, 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0x50, 0x24, 0xFF, 0xFF, 0x35, 0x00, 0x06, 575 | 0xF0, 0xFF, 0x50, 0x00, 0xCC, 0xFF, 0xE9, 0xA3, 0xD2, 0xFF, 0xFF, 0x50, 0x00, 0x24, 0xD6, 0xFF, 576 | 0xFC, 0x88, 0xF0, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x0A, 0x06, 0x00, 0xF6, 0xFF, 0x4C, 0x00, 0x5D, 577 | 0x44, 0x08, 0x04, 0x68, 0xFF, 0xFA, 0x13, 0x00, 0xBB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x66, 0x00, 578 | 0x00, 0x33, 0x6A, 0x99, 0x9B, 0x70, 0x26, 0x00, 0x00, 0x44, 0x5D, 0x22, 0x00, 0x00, 0x00, 0x00, 579 | 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 580 | 0x00, 0xBB, 0xFF, 0x59, 0x15, 0x4E, 0x48, 0x02, 0x00, 0xBB, 0xFF, 0x96, 0xF4, 0xFF, 0xFF, 0xDF, 581 | 0x1D, 0xBB, 0xFF, 0xFF, 0x8A, 0x77, 0xF2, 0xFF, 0x90, 0xBB, 0xFF, 0xA5, 0x00, 0x00, 0x9D, 0xFF, 582 | 0xAE, 0xBB, 0xFF, 0x5F, 0x00, 0x00, 0x6E, 0xFF, 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 583 | 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 584 | 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 0xAE, 0x2C, 0x55, 0x0C, 0xE5, 0xFF, 0x77, 0x7B, 585 | 0xB2, 0x2E, 0x2E, 0x3D, 0x17, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 586 | 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x2C, 587 | 0x55, 0x0C, 0x00, 0x00, 0xE5, 0xFF, 0x77, 0x00, 0x00, 0x7B, 0xB2, 0x2E, 0x00, 0x00, 0x2E, 0x3D, 588 | 0x17, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 589 | 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 590 | 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xCC, 0xFF, 0x5D, 0x48, 0x52, 591 | 0xFA, 0xFF, 0x50, 0xF8, 0xFF, 0xFF, 0xE1, 0x08, 0x79, 0x9B, 0x72, 0x13, 0x00, 0x44, 0x5D, 0x22, 592 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 593 | 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x1B, 0x3D, 0x3D, 594 | 0x02, 0xBB, 0xFF, 0x5D, 0x00, 0x1F, 0xE7, 0xFF, 0x72, 0x00, 0xBB, 0xFF, 0x5D, 0x15, 0xDD, 0xFF, 595 | 0x88, 0x00, 0x00, 0xBB, 0xFF, 0x5F, 0xCC, 0xFF, 0x9D, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0xDF, 0xFF, 596 | 0xFC, 0x0E, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0xFF, 0xE1, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0xBB, 0xFF, 597 | 0x7F, 0x1B, 0xEE, 0xFF, 0x68, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x52, 0xFF, 0xF8, 0x2E, 0x00, 598 | 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x9D, 0xFF, 0xDA, 0x0C, 0x44, 0x5D, 0x22, 0xBB, 0xFF, 0x5D, 0xBB, 599 | 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 600 | 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0xBB, 0xFF, 0x5D, 0x2E, 0x37, 0x00, 601 | 0x1B, 0x4E, 0x44, 0x00, 0x00, 0x15, 0x4E, 0x4A, 0x04, 0x00, 0xBB, 0xFC, 0x6E, 0xFC, 0xFF, 0xFF, 602 | 0xCE, 0x57, 0xF8, 0xFF, 0xFF, 0xDF, 0x13, 0xBB, 0xFF, 0xFC, 0x83, 0x8A, 0xFF, 0xFF, 0xFF, 0x90, 603 | 0x7D, 0xFC, 0xFF, 0x74, 0xBB, 0xFF, 0xA1, 0x00, 0x00, 0xC7, 0xFF, 0xB0, 0x00, 0x00, 0xAE, 0xFF, 604 | 0x9D, 0xBB, 0xFF, 0x5F, 0x00, 0x00, 0xB0, 0xFF, 0x94, 0x00, 0x00, 0xA1, 0xFF, 0xA1, 0xBB, 0xFF, 605 | 0x5D, 0x00, 0x00, 0xAE, 0xFF, 0x94, 0x00, 0x00, 0xA1, 0xFF, 0xA1, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 606 | 0xAE, 0xFF, 0x94, 0x00, 0x00, 0xA1, 0xFF, 0xA1, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xAE, 0xFF, 0x94, 607 | 0x00, 0x00, 0xA1, 0xFF, 0xA1, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0xAE, 0xFF, 0x94, 0x00, 0x00, 0xA1, 608 | 0xFF, 0xA1, 0x2E, 0x37, 0x00, 0x17, 0x4E, 0x4A, 0x04, 0x00, 0xBB, 0xFC, 0x6C, 0xFA, 0xFF, 0xFF, 609 | 0xE1, 0x1D, 0xBB, 0xFF, 0xFF, 0x8A, 0x74, 0xF2, 0xFF, 0x90, 0xBB, 0xFF, 0xA5, 0x00, 0x00, 0x9D, 610 | 0xFF, 0xAE, 0xBB, 0xFF, 0x5F, 0x00, 0x00, 0x6E, 0xFF, 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 611 | 0xFF, 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 612 | 0xFF, 0xAE, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 0xAE, 0x00, 0x00, 0x00, 0x35, 0x50, 0x44, 613 | 0x02, 0x00, 0x00, 0x00, 0x19, 0xCE, 0xFF, 0xFF, 0xFF, 0xE3, 0x3B, 0x00, 0x00, 0xC7, 0xFF, 0xD4, 614 | 0x6C, 0xB4, 0xFF, 0xEE, 0x13, 0x28, 0xFF, 0xFF, 0x22, 0x00, 0x00, 0xDA, 0xFF, 0x68, 0x4E, 0xFF, 615 | 0xFA, 0x00, 0x00, 0x00, 0xAA, 0xFF, 0x9B, 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0xAE, 0xFF, 0x99, 616 | 0x11, 0xFC, 0xFF, 0x3D, 0x00, 0x0C, 0xEB, 0xFF, 0x5B, 0x00, 0x99, 0xFF, 0xEE, 0xAA, 0xD8, 0xFF, 617 | 0xE3, 0x0C, 0x00, 0x04, 0x8A, 0xFA, 0xFF, 0xFF, 0xC1, 0x22, 0x00, 0x00, 0x00, 0x00, 0x02, 0x0C, 618 | 0x06, 0x00, 0x00, 0x00, 0x2E, 0x3B, 0x00, 0x19, 0x4E, 0x39, 0x00, 0x00, 0xBB, 0xFF, 0x70, 0xF4, 619 | 0xFF, 0xFF, 0xB0, 0x00, 0xBB, 0xFF, 0xF6, 0x7F, 0x88, 0xFF, 0xFF, 0x63, 0xBB, 0xFF, 0x90, 0x00, 620 | 0x00, 0xA3, 0xFF, 0xAA, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x6A, 0xFF, 0xB6, 0xBB, 0xFF, 0x5F, 0x00, 621 | 0x00, 0x74, 0xFF, 0xB4, 0xBB, 0xFF, 0xB2, 0x00, 0x00, 0xBD, 0xFF, 0xA3, 0xBB, 0xFF, 0xFF, 0xBF, 622 | 0xC3, 0xFF, 0xFF, 0x3B, 0xBB, 0xFF, 0x88, 0xE5, 0xFF, 0xFA, 0x72, 0x00, 0xBB, 0xFF, 0x5B, 0x00, 623 | 0x0C, 0x04, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 624 | 0x00, 0x00, 0x00, 0x00, 0x72, 0x9B, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x4E, 625 | 0x44, 0x00, 0x24, 0x3D, 0x13, 0x00, 0x3D, 0xF0, 0xFF, 0xFF, 0xC5, 0xB8, 0xFF, 0x50, 0x00, 0xE1, 626 | 0xFF, 0xD6, 0x72, 0xC3, 0xFF, 0xFF, 0x50, 0x33, 0xFF, 0xFF, 0x2A, 0x00, 0x06, 0xF6, 0xFF, 0x50, 627 | 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0xB6, 0xFF, 0x50, 0x4E, 0xFF, 0xFA, 0x00, 0x00, 0x00, 0xB8, 628 | 0xFF, 0x50, 0x26, 0xFF, 0xFF, 0x35, 0x00, 0x0A, 0xF6, 0xFF, 0x50, 0x00, 0xCC, 0xFF, 0xE9, 0xA3, 629 | 0xD2, 0xFA, 0xFF, 0x50, 0x00, 0x22, 0xD6, 0xFF, 0xFF, 0x8C, 0xE1, 0xFF, 0x50, 0x00, 0x00, 0x00, 630 | 0x0A, 0x06, 0x00, 0xF2, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xFF, 0x50, 0x00, 631 | 0x00, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x92, 0x9B, 632 | 0x30, 0x2E, 0x37, 0x00, 0x0E, 0x4E, 0x22, 0xBB, 0xFA, 0x35, 0xE9, 0xFF, 0x5F, 0xBB, 0xFF, 0xF4, 633 | 0xF4, 0xB4, 0x3B, 0xBB, 0xFF, 0xCC, 0x0C, 0x00, 0x00, 0xBB, 0xFF, 0x63, 0x00, 0x00, 0x00, 0xBB, 634 | 0xFF, 0x5D, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 635 | 0x00, 0xBB, 0xFF, 0x5D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x4E, 0x4E, 0x26, 0x00, 0x00, 0xAC, 636 | 0xFF, 0xFF, 0xFF, 0xFF, 0x92, 0x44, 0xFF, 0xF8, 0x55, 0x59, 0xA3, 0x44, 0x41, 0xFF, 0xFF, 0x79, 637 | 0x0C, 0x00, 0x00, 0x00, 0xB4, 0xFF, 0xFF, 0xF6, 0x88, 0x08, 0x00, 0x00, 0x4C, 0xC3, 0xFF, 0xFF, 638 | 0x9B, 0x04, 0x00, 0x00, 0x00, 0x74, 0xFF, 0xEB, 0x50, 0xD8, 0x9D, 0x7D, 0xC9, 0xFF, 0xB0, 0x37, 639 | 0xE9, 0xFF, 0xFF, 0xFF, 0xC1, 0x1B, 0x00, 0x00, 0x0A, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x02, 0xE1, 640 | 0x99, 0x00, 0x00, 0x00, 0x50, 0xFF, 0xB8, 0x3D, 0x39, 0x66, 0xFA, 0xFF, 0xFF, 0xFF, 0xF2, 0x41, 641 | 0xC1, 0xFF, 0xC5, 0x63, 0x5F, 0x00, 0x9B, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xA1, 0x00, 642 | 0x00, 0x00, 0x9B, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x9B, 0xFF, 0xA5, 0x00, 0x00, 0x00, 0x72, 0xFF, 643 | 0xFA, 0xA5, 0xB6, 0x00, 0x11, 0xC7, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x00, 0x37, 644 | 0x3D, 0x15, 0x00, 0x00, 0x22, 0x3D, 0x28, 0xEB, 0xFF, 0x57, 0x00, 0x00, 0x94, 0xFF, 0xA7, 0xEB, 645 | 0xFF, 0x57, 0x00, 0x00, 0x94, 0xFF, 0xA7, 0xEB, 0xFF, 0x57, 0x00, 0x00, 0x94, 0xFF, 0xA7, 0xEB, 646 | 0xFF, 0x57, 0x00, 0x00, 0x96, 0xFF, 0xA7, 0xEB, 0xFF, 0x57, 0x00, 0x00, 0xA1, 0xFF, 0xA7, 0xDF, 647 | 0xFF, 0x79, 0x00, 0x00, 0xD6, 0xFF, 0xA7, 0x9F, 0xFF, 0xF8, 0xA7, 0xCC, 0xFF, 0xFF, 0xA7, 0x19, 648 | 0xCC, 0xFF, 0xFF, 0xD2, 0x4C, 0xFF, 0xA7, 0x00, 0x00, 0x08, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x3D, 649 | 0x3D, 0x13, 0x00, 0x00, 0x00, 0x33, 0x3D, 0x1D, 0xB8, 0xFF, 0x8E, 0x00, 0x00, 0x0A, 0xFA, 0xFF, 650 | 0x3D, 0x57, 0xFF, 0xE5, 0x00, 0x00, 0x5D, 0xFF, 0xDF, 0x00, 0x04, 0xEE, 0xFF, 0x3D, 0x00, 0xB2, 651 | 0xFF, 0x7D, 0x00, 0x00, 0x90, 0xFF, 0x92, 0x0A, 0xFA, 0xFF, 0x1F, 0x00, 0x00, 0x30, 0xFF, 0xE7, 652 | 0x5B, 0xFF, 0xBD, 0x00, 0x00, 0x00, 0x00, 0xD0, 0xFF, 0xBF, 0xFF, 0x5B, 0x00, 0x00, 0x00, 0x00, 653 | 0x6E, 0xFF, 0xFF, 0xF2, 0x04, 0x00, 0x00, 0x00, 0x00, 0x11, 0xFA, 0xFF, 0x94, 0x00, 0x00, 0x00, 654 | 0x2E, 0x3D, 0x15, 0x00, 0x00, 0x2E, 0x3D, 0x28, 0x00, 0x00, 0x22, 0x3D, 0x28, 0xA7, 0xFF, 0x8A, 655 | 0x00, 0x02, 0xF6, 0xFF, 0xD2, 0x00, 0x00, 0xA7, 0xFF, 0x77, 0x57, 0xFF, 0xC9, 0x00, 0x39, 0xFF, 656 | 0xE9, 0xFF, 0x0E, 0x00, 0xF2, 0xFF, 0x2E, 0x0E, 0xFC, 0xFC, 0x0C, 0x77, 0xFF, 0x77, 0xFF, 0x50, 657 | 0x35, 0xFF, 0xE7, 0x00, 0x00, 0xC5, 0xFF, 0x50, 0xB4, 0xEB, 0x13, 0xFF, 0x8E, 0x77, 0xFF, 0x9D, 658 | 0x00, 0x00, 0x7F, 0xFF, 0x85, 0xF4, 0xA7, 0x00, 0xDA, 0xB8, 0xAC, 0xFF, 0x50, 0x00, 0x00, 0x37, 659 | 0xFF, 0xB2, 0xFF, 0x6E, 0x00, 0xA3, 0xF2, 0xDF, 0xFA, 0x06, 0x00, 0x00, 0x00, 0xF2, 0xFA, 0xFF, 660 | 0x37, 0x00, 0x61, 0xFF, 0xFF, 0xBD, 0x00, 0x00, 0x00, 0x00, 0xA7, 0xFF, 0xF8, 0x02, 0x00, 0x30, 661 | 0xFF, 0xFF, 0x74, 0x00, 0x00, 0x2C, 0x3D, 0x2E, 0x00, 0x00, 0x02, 0x3D, 0x3D, 0x19, 0x52, 0xFF, 662 | 0xFC, 0x2A, 0x00, 0x7B, 0xFF, 0xE9, 0x13, 0x00, 0xAA, 0xFF, 0xC1, 0x1F, 0xF6, 0xFF, 0x52, 0x00, 663 | 0x00, 0x15, 0xE9, 0xFF, 0xDF, 0xFF, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x5D, 0xFF, 0xFF, 0xF2, 0x13, 664 | 0x00, 0x00, 0x00, 0x00, 0x96, 0xFF, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x00, 0x41, 0xFF, 0xFA, 0x9D, 665 | 0xFF, 0xDF, 0x0C, 0x00, 0x0A, 0xDD, 0xFF, 0x7F, 0x02, 0xD4, 0xFF, 0x94, 0x00, 0x92, 0xFF, 0xDD, 666 | 0x06, 0x00, 0x39, 0xFF, 0xFF, 0x3B, 0x3B, 0x3D, 0x17, 0x00, 0x00, 0x00, 0x37, 0x3D, 0x1D, 0xB6, 667 | 0xFF, 0xA7, 0x00, 0x00, 0x15, 0xFC, 0xFF, 0x3D, 0x4A, 0xFF, 0xF8, 0x04, 0x00, 0x66, 0xFF, 0xDF, 668 | 0x00, 0x02, 0xE3, 0xFF, 0x52, 0x00, 0xBB, 0xFF, 0x7F, 0x00, 0x00, 0x7F, 0xFF, 0xA7, 0x0E, 0xFC, 669 | 0xFF, 0x22, 0x00, 0x00, 0x1D, 0xFC, 0xF8, 0x66, 0xFF, 0xC1, 0x00, 0x00, 0x00, 0x00, 0xB6, 0xFF, 670 | 0xE3, 0xFF, 0x61, 0x00, 0x00, 0x00, 0x00, 0x4A, 0xFF, 0xFF, 0xF8, 0x08, 0x00, 0x00, 0x00, 0x00, 671 | 0x02, 0xE3, 0xFF, 0x9F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0xE9, 0xFF, 0x3D, 0x00, 0x00, 0x00, 672 | 0x1D, 0x46, 0xA7, 0xFF, 0xD6, 0x00, 0x00, 0x00, 0x00, 0x63, 0xFF, 0xFF, 0xEE, 0x35, 0x00, 0x00, 673 | 0x00, 0x00, 0x39, 0x9B, 0x72, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x3D, 0x3D, 0x3D, 0x3D, 674 | 0x3D, 0x2A, 0x57, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xAE, 0x33, 0x94, 0x94, 0x94, 0xFA, 0xFF, 0x83, 675 | 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xCC, 0x02, 0x00, 0x00, 0x3F, 0xFF, 0xF0, 0x1F, 0x00, 0x00, 0x13, 676 | 0xE7, 0xFF, 0x57, 0x00, 0x00, 0x00, 0xB0, 0xFF, 0x9B, 0x00, 0x00, 0x00, 0x68, 0xFF, 0xFF, 0xB6, 677 | 0xAE, 0xAE, 0xA1, 0x9B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xEB, 0x00, 0x00, 0x17, 0x83, 0xA5, 0x35, 678 | 0x00, 0x00, 0xBF, 0xFF, 0xFF, 0x4E, 0x00, 0x00, 0xF2, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0xF2, 0xFF, 679 | 0x00, 0x00, 0x00, 0x00, 0xF2, 0xFF, 0x00, 0x00, 0x02, 0x50, 0xFF, 0xF6, 0x00, 0x00, 0xB4, 0xFF, 680 | 0xC9, 0x46, 0x00, 0x00, 0x7D, 0xEB, 0xFF, 0x96, 0x00, 0x00, 0x00, 0x11, 0xFA, 0xFF, 0x00, 0x00, 681 | 0x00, 0x00, 0xF2, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xF2, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xEE, 0xFF, 682 | 0x7B, 0x19, 0x00, 0x00, 0x88, 0xFF, 0xFF, 0x50, 0x00, 0x00, 0x00, 0x30, 0x52, 0x1B, 0x39, 0x52, 683 | 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 684 | 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0xA7, 0xF2, 0x39, 0x52, 0x3D, 0xA5, 685 | 0x7F, 0x11, 0x00, 0x00, 0x5B, 0xFF, 0xFF, 0xA1, 0x00, 0x00, 0x00, 0x39, 0xFF, 0xB8, 0x00, 0x00, 686 | 0x00, 0x0C, 0xFF, 0xBB, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xC1, 0x00, 0x00, 0x00, 0x08, 0xFF, 0xF8, 687 | 0x41, 0x02, 0x00, 0x00, 0x57, 0xD2, 0xFF, 0xA7, 0x00, 0x00, 0xAE, 0xFF, 0xE5, 0x74, 0x00, 0x0C, 688 | 0xFF, 0xE9, 0x06, 0x00, 0x00, 0x0C, 0xFF, 0xBB, 0x00, 0x00, 0x00, 0x0C, 0xFF, 0xBB, 0x00, 0x00, 689 | 0x1D, 0x8E, 0xFF, 0xB6, 0x00, 0x00, 0x5D, 0xFF, 0xFF, 0x72, 0x00, 0x00, 0x1F, 0x50, 0x28, 0x00, 690 | 0x00, 0x00, 0x00, 0x46, 0x90, 0x5D, 0x15, 0x00, 0x00, 0x2E, 0x4E, 0xFF, 0xFF, 0xFF, 0xFA, 0xB4, 691 | 0xAE, 0xF0, 0x41, 0x52, 0x08, 0x46, 0xA3, 0xEE, 0xE7, 0x79, 0x00, 0x44, 0xBB, 0xD2, 0x88, 0x04, 692 | 0x24, 0xFA, 0xC5, 0x9D, 0xFC, 0x83, 0x5B, 0xFF, 0x11, 0x00, 0xAC, 0xB4, 0x3F, 0xFF, 0x7F, 0x4A, 693 | 0xE5, 0xA3, 0x00, 0x92, 0xFF, 0xFF, 0xDD, 0x1F, 0x00, 0x00, 0x0C, 0x24, 0x00, 0x00, 0x00, 0x0E, 694 | 0x4E, 0x6F, 0x74, 0x6F, 0x20, 0x53, 0x61, 0x6E, 0x73, 0x20, 0x42, 0x6F, 0x6C, 0x64, 0x00, 0x0D, 695 | 0x4E, 0x6F, 0x74, 0x6F, 0x53, 0x61, 0x6E, 0x73, 0x2D, 0x42, 0x6F, 0x6C, 0x64, 0x01, 696 | }; 697 | -------------------------------------------------------------------------------- /ESP32RemoteForVictron/general_settings.h: -------------------------------------------------------------------------------- 1 | #define GENERAL_SETTINGS_AMOLED_VERSION 1 // set to 1 if you are using the Lilygo T-Display S3 AMOLED v1 2 | // set to 2 if you are using the Lilygo T-Display S3 AMOLED v2 3 | // for more information please see: https://www.lilygo.cc/en-ca/products/t-display-s3-amoled 4 | 5 | #define GENERAL_SETTINGS_USB_ON_THE_LEFT true // set to true to plug in the USB power cable from the left, set to false to plug in the USB power cable from the right 6 | 7 | #define GENERAL_SETTINGS_ALLOW_CHANGING_INVERTER_AND_CHARGER_MODES true // set to true to allow the user to change the Multiplus charger and inverter modes with the buttons on this device, otherwise to prevent that set to false 8 | #define GENERAL_SETTINGS_SHOW_CHARGER_MODE true // set to true to show the charger mode, otherwise set to false to hide the charger mode 9 | #define GENERAL_SETTINGS_SHOW_INVERTER_MODE true // set to true to show the inverter mode, otherwise set to false to hide the inverter mode 10 | 11 | #define GENERAL_SETTINGS_SECONDS_BETWEEN_DISPLAY_UPDATES 1 // seconds between display updates 12 | 13 | #define GENERAL_SETTINGS_IF_OVER_1000_WATTS_REPORT_KW true // if value being reported is over 1000 Watts then if true report in Kilo Watts otherwise report in Watts 14 | 15 | #define GENERAL_SETTINGS_NUMBER_DECIMAL_PLACES_FOR_KW_REPORTING 1 // if GENERAL_SETTINGS_IF_OVER_1000_WATTS_REPORT_KW is true, then show KiloWatts with this many decimal places 16 | 17 | #define GENERAL_SETTINGS_ROUND_NUMBERS true // if true numbers will be rounded, otherwise they will be truncated (i.e. 9.55 rounded = 9.6; 9.55 truncated = 9.5) 18 | 19 | #define GENERAL_SETTINGS_SHOW_BATTERY_AS_YELLOW 40 // show battery as yellow if its percentage charged is at or below this number 20 | 21 | #define GENERAL_SETTINGS_SHOW_BATTERY_AS_RED 20 // show battery as red if its percentage charged is at or below this number 22 | 23 | #define GENERAL_SETTINGS_ADDITIONAL_INFO 2 // show additional information under battery percent: 24 | // 0 = do not show any additional info 25 | // 1 = show Time To Go (remaining battery time); note this value is only available when the battery is discharging 26 | // 2 = show Solar Charger (mppt) / Multiplus charging state: Off/Fault/Bulk/Absorption/Float/Storage/Equalize/ESS 27 | // 3 = show battery temperature 28 | // 4 = show battery power, positive number means power going into the battery, negative number means power going out of the battery 29 | 30 | // if any of the following are not used in your installation then you can set the associated value(s) below to false to reduce unneeded MQTT traffic: 31 | #define GENERAL_SETTINGS_GRID_IN_L1_IS_USED true // set to true if Grid IN L1 is used in your installation, otherwise set to false 32 | #define GENERAL_SETTINGS_GRID_IN_L2_IS_USED true // set to true if Grid IN L2 is used in your installation, otherwise set to false 33 | #define GENERAL_SETTINGS_GRID_IN_L3_IS_USED true // set to true if Grid IN L3 is used in your installation, otherwise set to false 34 | #define GENERAL_SETTINGS_PV_IS_USED true // set to true if PV (Solar) is used in your installation, otherwise set to false 35 | #define GENERAL_SETTINGS_AC_OUT_L1_IS_USED true // set to true if AC OUT L1 is used in your installation, otherwise set to false 36 | #define GENERAL_SETTINGS_AC_OUT_L2_IS_USED true // set to true if AC OUT L2 is used in your installation, otherwise set to false 37 | #define GENERAL_SETTINGS_AC_OUT_L3_IS_USED true // set to true if AC OUT L3 is used in your installation, otherwise set to false 38 | 39 | #define GENERAL_SETTINGS_SHOW_SPLASH_SCREEN true // set to true show the splash screen on initial startup, set to false to not show the splash screen 40 | 41 | #define GENERAL_SETTINGS_TURN_ON_DISPLAY_AT_SPECIFIC_TIMES_ONLY false // set to true to turn on the display between the times indentified below, set to false to leave the display always on 42 | // 43 | // if GENERAL_SETTINGS_TURN_ON_DISPLAY_AT_SPECIFIC_TIMES_ONLY is true, then the following will also be needed: 44 | // 45 | #define GENERAL_SETTINGS_USE_DEEP_SLEEP true // set to true to use deep sleep (see notes below) 46 | // 47 | #define GENERAL_SETTINGS_WAKE_TIME "06:15" // the time at which the display will automatically be turned on - in 24 hour format between 00:00 and 23:59 48 | #define GENERAL_SETTINGS_SLEEP_TIME "23:45" // the time at which the display will automatically be turned off - in 24 hour format between 00:00 and 23:59 49 | // 50 | // NOTES: 51 | // 52 | // 1. if GENERAL_SETTINGS_WAKE_TIME and GENERAL_SETTINGS_SLEEP_TIME are the same then the display will 53 | // default to being off except for a minute following startup or when a button is pressed 54 | // 55 | // 2. GENERAL_SETTINGS_WAKE_TIME does not need to be less than the GENERAL_SETTINGS_SLEEP_TIME 56 | // 57 | // 3. to turn on the display outside the times prescribed above, press 58 | // the top button if GENERAL_SETTINGS_USB_ON_THE_LEFT = true 59 | // or 60 | // the bottom button if GENERAL_SETTINGS_USB_ON_THE_LEFT = false 61 | // 62 | // 4. If GENERAL_SETTINGS_USE_DEEP_SLEEP = true 63 | // The esp32 will use far less power when in sleep mode 64 | // However, the wake up time will not be exact as there are inherent inaccuracies with the precision of the ESP32's 65 | // internal real time clock. For example, in testing with my device a 23 hour and 50 minute sleep cycle resulted 66 | // in my device waking up 15 minutes and 9 seconds earlier than it should have. However, shorter sleep periods should 67 | // have smaller discrepancies. 68 | // If deep sleep is not used, the esp32 will use more power when the display is off, but automatic wakeup times 69 | // will be precise. 70 | // 71 | #define GENERAL_SETTINGS_MY_TIME_ZONE "EST5EDT,M3.2.0,M11.1.0" // Time Zone; supported Timezones are listed here: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv 72 | // 73 | #define GENERAL_SETTINGS_PRIMARY_TIME_SERVER "time.nrc.ca" // primary ntp time server 74 | #define GENERAL_SETTINGS_SECONDARY_TIME_SERVER "ca.pool.ntp.org" // secondary ntp time server 75 | #define GENERAL_SETTINGS_TERTIARY_TIME_SERVER "north-america.pool.ntp.org" // tertiary ntp time server 76 | // alternative ntp time servers / server pools that may be used above: 77 | // "time.nrc.ca" // Ottawa, Ontario, Canada 78 | // "ca.pool.ntp.org" // Canada 79 | // "asia.pool.ntp.org" 80 | // "europe.pool.ntp.org" 81 | // "north-america.pool.ntp.org" 82 | // "oceania.pool.ntp.org" 83 | // "south-america.pool.ntp.org" 84 | // "pool.ntp.org" // World wide 85 | 86 | #define GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS true // Keep Alive requests are needed periodically by Venus to continue to publish MQTT data 87 | // 88 | // Generally, this option should be set to true to have this program periodically send the needed Keep Alive requests. 89 | // However, if another system (for example Home Assistant) is already sending periodic Keep Alive requests then 90 | // this option may be set to false to cut down on a very minor amount of network traffic. 91 | // 92 | // Notes: 93 | // 1. even if this option is set to false, Keep Alive requests will still be sent by this program 94 | // in specific cases to get data as needed 95 | // 96 | // 2. it is recommended to set this option to true even if another system is sending Keep Alive requests, 97 | // however, this is not absolutely necessary 98 | // 99 | // 3. if this option is set to false and you see the message "MQTT data updates have stopped" appear for several seconds 100 | // on the screen and then be replaced by a normal readout, it likely means that the other system that is supposed to be 101 | // sending the keep alive requests has stopped doing so 102 | // 103 | // 4. regardless of if this option is set to true or false, if you see the message "Awaiting Wi-Fi connection" appear 104 | // on your screen and stay there it means this device cannot access your Wi-Fi network 105 | // 106 | // 5. regardless of if this option is set to true or false, if you see the message "Awaiting MQTT connection" appear 107 | // on your screen and stay there it likely means Venus itself is no longer transmitting MQTT data 108 | #define GENERAL_SETTINGS_SEND_PERIODICAL_KEEP_ALIVE_REQUESTS_INTERVAL 30000 // Time between keep alive requests in ms 109 | 110 | #define GENERAL_SETTINGS_ENABLE_OVER_THE_AIR_UPDATES true // set to true to enable OTA updates, set to false to disable OTA updates 111 | 112 | #define GENERAL_SETTINGS_DEBUG_OUTPUT_LEVEL 1 // set to: 0 for no debug output 113 | // 1 for general debug output 114 | // 2 for verbose debug output 115 | 116 | #define GENERAL_SETTINGS_SERIAL_MONITOR_SPEED 115200 // serial monitor speed 117 | -------------------------------------------------------------------------------- /ESP32RemoteForVictron/pins_config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /***********************config*************************/ 4 | #define LCD_USB_QSPI_DREVER 1 5 | 6 | #define SPI_FREQUENCY 75000000 7 | #define TFT_SPI_MODE SPI_MODE0 8 | #define TFT_SPI_HOST SPI2_HOST 9 | 10 | #define EXAMPLE_LCD_H_RES 536 11 | #define EXAMPLE_LCD_V_RES 240 12 | #define LVGL_LCD_BUF_SIZE (EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES) 13 | 14 | /***********************config*************************/ 15 | 16 | #define TFT_HEIGHT 240 17 | #define TFT_WIDTH 536 18 | #define SEND_BUF_SIZE (0x4000) //(LCD_WIDTH * LCD_HEIGHT + 8) / 10 19 | 20 | #define TFT_TE 9 21 | #define TFT_SDO 8 22 | 23 | #define TFT_DC 7 24 | #define TFT_RES 17 25 | #define TFT_CS 6 26 | #define TFT_MOSI 18 27 | #define TFT_SCK 47 28 | 29 | #define TFT_QSPI_CS 6 30 | #define TFT_QSPI_SCK 47 31 | #define TFT_QSPI_D0 18 32 | #define TFT_QSPI_D1 7 33 | #define TFT_QSPI_D2 48 34 | #define TFT_QSPI_D3 5 35 | #define TFT_QSPI_RST 17 36 | 37 | #define PIN_LED 38 38 | #define PIN_BAT_VOLT 4 39 | 40 | #define PIN_BUTTON_1 0 41 | #define PIN_BUTTON_2 21 -------------------------------------------------------------------------------- /ESP32RemoteForVictron/rm67162.cpp: -------------------------------------------------------------------------------- 1 | #include "rm67162.h" 2 | #include "SPI.h" 3 | #include "Arduino.h" 4 | #include "driver/spi_master.h" 5 | 6 | const static lcd_cmd_t rm67162_spi_init[] = { 7 | {0xFE, {0x00}, 0x01}, // PAGE 8 | // {0x35, {0x00}, 0x00}, //TE ON 9 | // {0x34, {0x00}, 0x00}, //TE OFF 10 | {0x36, {0x00}, 0x01}, // Scan Direction Control 11 | {0x3A, {0x75}, 0x01}, // Interface Pixel Format 16bit/pixel 12 | // {0x3A, {0x76}, 0x01}, //Interface Pixel Format 18bit/pixel 13 | // {0x3A, {0x77}, 0x01}, //Interface Pixel Format 24bit/pixel 14 | {0x51, {0x00}, 0x01}, // Write Display Brightness MAX_VAL=0XFF 15 | {0x11, {0x00}, 0x01 | 0x80}, // Sleep Out 16 | {0x29, {0x00}, 0x01 | 0x80}, // Display on 17 | {0x51, {0xD0}, 0x01}, // Write Display Brightness MAX_VAL=0XFF 18 | }; 19 | 20 | const static lcd_cmd_t rm67162_qspi_init[] = { 21 | {0x11, {0x00}, 0x80}, // Sleep Out 22 | // {0x44, {0x01, 0x66}, 0x02}, //Set_Tear_Scanline 23 | // {0x35, {0x00}, 0x00}, //TE ON 24 | // {0x34, {0x00}, 0x00}, //TE OFF 25 | // {0x36, {0x00}, 0x01}, //Scan Direction Control 26 | {0x3A, {0x55}, 0x01}, // Interface Pixel Format 16bit/pixel 27 | // {0x3A, {0x66}, 0x01}, //Interface Pixel Format 18bit/pixel 28 | // {0x3A, {0x77}, 0x01}, //Interface Pixel Format 24bit/pixel 29 | {0x51, {0x00}, 0x01}, // Write Display Brightness MAX_VAL=0XFF 30 | {0x29, {0x00}, 0x80}, // Display on 31 | {0x51, {0xD0}, 0x01}, // Write Display Brightness MAX_VAL=0XFF 32 | }; 33 | 34 | static spi_device_handle_t spi; 35 | 36 | static void WriteComm(uint8_t data) 37 | { 38 | TFT_CS_L; 39 | SPI.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); 40 | TFT_DC_L; 41 | SPI.write(data); 42 | TFT_DC_H; 43 | SPI.endTransaction(); 44 | TFT_CS_H; 45 | } 46 | 47 | static void WriteData(uint8_t data) 48 | { 49 | TFT_CS_L; 50 | SPI.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); 51 | TFT_DC_H; 52 | SPI.write(data); 53 | SPI.endTransaction(); 54 | TFT_CS_H; 55 | } 56 | 57 | static void WriteData16(uint16_t data) 58 | { 59 | 60 | TFT_CS_L; 61 | SPI.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); 62 | TFT_DC_H; 63 | SPI.write16(data); 64 | SPI.endTransaction(); 65 | TFT_CS_H; 66 | } 67 | 68 | static void lcd_send_cmd(uint32_t cmd, uint8_t *dat, uint32_t len) 69 | { 70 | #if LCD_USB_QSPI_DREVER == 1 71 | TFT_CS_L; 72 | spi_transaction_t t; 73 | memset(&t, 0, sizeof(t)); 74 | t.flags = (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR); 75 | t.cmd = 0x02; 76 | t.addr = cmd << 8; 77 | // Serial.printf("t.addr:0x%X\r\n", t.addr); 78 | if (len != 0) 79 | { 80 | t.tx_buffer = dat; 81 | t.length = 8 * len; 82 | } 83 | else 84 | { 85 | t.tx_buffer = NULL; 86 | t.length = 0; 87 | } 88 | spi_device_polling_transmit(spi, &t); 89 | TFT_CS_H; 90 | #else 91 | WriteComm(cmd); 92 | if (len != 0) 93 | { 94 | for (int i = 0; i < len; i++) 95 | WriteData(dat[i]); 96 | } 97 | #endif 98 | } 99 | 100 | void rm67162_init(void) 101 | { 102 | pinMode(TFT_CS, OUTPUT); 103 | pinMode(TFT_RES, OUTPUT); 104 | 105 | TFT_RES_L; 106 | delay(300); 107 | TFT_RES_H; 108 | delay(200); 109 | 110 | #if LCD_USB_QSPI_DREVER == 1 111 | esp_err_t ret; 112 | 113 | spi_bus_config_t buscfg = { 114 | .data0_io_num = TFT_QSPI_D0, 115 | .data1_io_num = TFT_QSPI_D1, 116 | .sclk_io_num = TFT_QSPI_SCK, 117 | .data2_io_num = TFT_QSPI_D2, 118 | .data3_io_num = TFT_QSPI_D3, 119 | .max_transfer_sz = (SEND_BUF_SIZE * 16) + 8, 120 | .flags = SPICOMMON_BUSFLAG_MASTER | SPICOMMON_BUSFLAG_GPIO_PINS /* | 121 | SPICOMMON_BUSFLAG_QUAD */ 122 | , 123 | }; 124 | spi_device_interface_config_t devcfg = { 125 | .command_bits = 8, 126 | .address_bits = 24, 127 | .mode = TFT_SPI_MODE, 128 | .clock_speed_hz = SPI_FREQUENCY, 129 | .spics_io_num = -1, 130 | // .spics_io_num = TFT_QSPI_CS, 131 | .flags = SPI_DEVICE_HALFDUPLEX, 132 | .queue_size = 17, 133 | }; 134 | ret = spi_bus_initialize(TFT_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO); 135 | ESP_ERROR_CHECK(ret); 136 | ret = spi_bus_add_device(TFT_SPI_HOST, &devcfg, &spi); 137 | ESP_ERROR_CHECK(ret); 138 | 139 | #else 140 | SPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS); 141 | SPI.setFrequency(SPI_FREQUENCY); 142 | pinMode(TFT_DC, OUTPUT); 143 | #endif 144 | // Initialize the screen multiple times to prevent initialization failure 145 | int i = 3; 146 | while (i--) { 147 | #if LCD_USB_QSPI_DREVER == 1 148 | const lcd_cmd_t *lcd_init = rm67162_qspi_init; 149 | for (int i = 0; i < sizeof(rm67162_qspi_init) / sizeof(lcd_cmd_t); i++) 150 | #else 151 | const lcd_cmd_t *lcd_init = rm67162_spi_init; 152 | for (int i = 0; i < sizeof(rm67162_spi_init) / sizeof(lcd_cmd_t); i++) 153 | #endif 154 | { 155 | lcd_send_cmd(lcd_init[i].cmd, 156 | (uint8_t *)lcd_init[i].data, 157 | lcd_init[i].len & 0x7f); 158 | 159 | if (lcd_init[i].len & 0x80) 160 | delay(120); 161 | } 162 | } 163 | 164 | } 165 | 166 | void lcd_setRotation(uint8_t r) 167 | { 168 | uint8_t gbr = TFT_MAD_RGB; 169 | 170 | switch (r) 171 | { 172 | case 0: // Portrait 173 | // WriteData(gbr); 174 | break; 175 | case 1: // Landscape (Portrait + 90) 176 | gbr = TFT_MAD_MX | TFT_MAD_MV | gbr; 177 | break; 178 | case 2: // Inverter portrait 179 | gbr = TFT_MAD_MX | TFT_MAD_MY | gbr; 180 | break; 181 | case 3: // Inverted landscape 182 | gbr = TFT_MAD_MV | TFT_MAD_MY | gbr; 183 | break; 184 | } 185 | lcd_send_cmd(TFT_MADCTL, &gbr, 1); 186 | } 187 | 188 | void lcd_address_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) 189 | { 190 | lcd_cmd_t t[3] = { 191 | {0x2a, {x1 >> 8, x1, x2 >> 8, x2}, 0x04}, 192 | {0x2b, {y1 >> 8, y1, y2 >> 8, y2}, 0x04}, 193 | {0x2c, {0x00}, 0x00}, 194 | }; 195 | 196 | for (uint32_t i = 0; i < 3; i++) 197 | { 198 | lcd_send_cmd(t[i].cmd, t[i].data, t[i].len); 199 | } 200 | } 201 | 202 | void lcd_fill(uint16_t xsta, 203 | uint16_t ysta, 204 | uint16_t xend, 205 | uint16_t yend, 206 | uint16_t color) 207 | { 208 | 209 | uint16_t w = xend - xsta; 210 | uint16_t h = yend - ysta; 211 | uint16_t *color_p = (uint16_t *)heap_caps_malloc(w * h * 2, MALLOC_CAP_INTERNAL); 212 | memset(color_p, color, w * h * 2); 213 | lcd_PushColors(xsta, ysta, w, h, color_p); 214 | free(color_p); 215 | } 216 | 217 | void lcd_DrawPoint(uint16_t x, uint16_t y, uint16_t color) 218 | { 219 | lcd_address_set(x, y, x + 1, y + 1); 220 | lcd_PushColors(&color, 1); 221 | } 222 | 223 | void lcd_PushColors(uint16_t x, 224 | uint16_t y, 225 | uint16_t width, 226 | uint16_t high, 227 | uint16_t *data) 228 | { 229 | #if LCD_USB_QSPI_DREVER == 1 230 | bool first_send = 1; 231 | size_t len = width * high; 232 | uint16_t *p = (uint16_t *)data; 233 | 234 | lcd_address_set(x, y, x + width - 1, y + high - 1); 235 | TFT_CS_L; 236 | do 237 | { 238 | size_t chunk_size = len; 239 | spi_transaction_ext_t t = {0}; 240 | memset(&t, 0, sizeof(t)); 241 | if (first_send) 242 | { 243 | t.base.flags = 244 | SPI_TRANS_MODE_QIO /* | SPI_TRANS_MODE_DIOQIO_ADDR */; 245 | t.base.cmd = 0x32 /* 0x12 */; 246 | t.base.addr = 0x002C00; 247 | first_send = 0; 248 | } 249 | else 250 | { 251 | t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD | 252 | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY; 253 | t.command_bits = 0; 254 | t.address_bits = 0; 255 | t.dummy_bits = 0; 256 | } 257 | if (chunk_size > SEND_BUF_SIZE) 258 | { 259 | chunk_size = SEND_BUF_SIZE; 260 | } 261 | t.base.tx_buffer = p; 262 | t.base.length = chunk_size * 16; 263 | 264 | // spi_device_queue_trans(spi, (spi_transaction_t *)&t, portMAX_DELAY); 265 | spi_device_polling_transmit(spi, (spi_transaction_t *)&t); 266 | len -= chunk_size; 267 | p += chunk_size; 268 | } while (len > 0); 269 | TFT_CS_H; 270 | 271 | #else 272 | lcd_address_set(x, y, x + width - 1, y + high - 1); 273 | TFT_CS_L; 274 | SPI.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); 275 | TFT_DC_H; 276 | SPI.writeBytes((uint8_t *)data, width * high * 2); 277 | SPI.endTransaction(); 278 | TFT_CS_H; 279 | #endif 280 | } 281 | 282 | void lcd_PushColors(uint16_t *data, uint32_t len) 283 | { 284 | #if LCD_USB_QSPI_DREVER == 1 285 | bool first_send = 1; 286 | uint16_t *p = (uint16_t *)data; 287 | TFT_CS_L; 288 | do 289 | { 290 | size_t chunk_size = len; 291 | spi_transaction_ext_t t = {0}; 292 | memset(&t, 0, sizeof(t)); 293 | if (first_send) 294 | { 295 | t.base.flags = 296 | SPI_TRANS_MODE_QIO /* | SPI_TRANS_MODE_DIOQIO_ADDR */; 297 | t.base.cmd = 0x32 /* 0x12 */; 298 | t.base.addr = 0x002C00; 299 | first_send = 0; 300 | } 301 | else 302 | { 303 | t.base.flags = SPI_TRANS_MODE_QIO | SPI_TRANS_VARIABLE_CMD | 304 | SPI_TRANS_VARIABLE_ADDR | SPI_TRANS_VARIABLE_DUMMY; 305 | t.command_bits = 0; 306 | t.address_bits = 0; 307 | t.dummy_bits = 0; 308 | } 309 | if (chunk_size > SEND_BUF_SIZE) 310 | { 311 | chunk_size = SEND_BUF_SIZE; 312 | } 313 | t.base.tx_buffer = p; 314 | t.base.length = chunk_size * 16; 315 | 316 | // spi_device_queue_trans(spi, (spi_transaction_t *)&t, portMAX_DELAY); 317 | spi_device_polling_transmit(spi, (spi_transaction_t *)&t); 318 | len -= chunk_size; 319 | p += chunk_size; 320 | } while (len > 0); 321 | TFT_CS_H; 322 | 323 | #else 324 | TFT_CS_L; 325 | SPI.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, TFT_SPI_MODE)); 326 | TFT_DC_H; 327 | SPI.writeBytes((uint8_t *)data, len * 2); 328 | SPI.endTransaction(); 329 | TFT_CS_H; 330 | #endif 331 | } 332 | 333 | void lcd_sleep() 334 | { 335 | lcd_send_cmd(0x10, NULL, 0); 336 | } 337 | 338 | void lcd_brightness(uint8_t bright) 339 | { 340 | lcd_send_cmd(0x51, &bright, 0x01); 341 | } 342 | 343 | void lcd_set_colour_enhance(uint8_t enh) 344 | { 345 | lcd_send_cmd(0x58, &enh, 0x01); 346 | } 347 | 348 | void lcd_display_off() 349 | { 350 | lcd_send_cmd(0x28, NULL, 0x01); 351 | } 352 | 353 | void lcd_display_on() 354 | { 355 | lcd_send_cmd(0x29, NULL, 0x01); 356 | } 357 | 358 | void lcd_display_invert_on() 359 | { 360 | lcd_send_cmd(0x21, NULL, 0x01); 361 | } 362 | 363 | void lcd_display_invert_off() 364 | { 365 | lcd_send_cmd(0x20, NULL, 0x01); 366 | } 367 | 368 | void lcd_display_set_colour_enhance_low_byte(uint8_t ce_low_byte) 369 | { 370 | lcd_send_cmd(0x5A, &ce_low_byte, 0x01); 371 | } 372 | 373 | void lcd_display_set_colour_enhance_high_byte(uint8_t ce_high_byte) 374 | { 375 | lcd_send_cmd(0x5B, &ce_high_byte, 0x01); 376 | } 377 | 378 | void lcd_display_high_brightness_mode_on(uint8_t hbm_en) 379 | { 380 | lcd_send_cmd(0xB0, &hbm_en, 0x01); 381 | } 382 | 383 | void lcd_display_high_brightness_mode_off(uint8_t hbm_en) 384 | { 385 | lcd_send_cmd(0xB0, &hbm_en, 0x01); 386 | } 387 | -------------------------------------------------------------------------------- /ESP32RemoteForVictron/rm67162.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stdint.h" 4 | #include "pins_config.h" 5 | 6 | #define TFT_MADCTL 0x36 7 | #define TFT_MAD_MY 0x80 8 | #define TFT_MAD_MX 0x40 9 | #define TFT_MAD_MV 0x20 10 | #define TFT_MAD_ML 0x10 11 | #define TFT_MAD_BGR 0x08 12 | #define TFT_MAD_MH 0x04 13 | #define TFT_MAD_RGB 0x00 14 | 15 | #define TFT_INVOFF 0x20 16 | #define TFT_INVON 0x21 17 | 18 | #define TFT_SCK_H digitalWrite(TFT_SCK, 1); 19 | #define TFT_SCK_L digitalWrite(TFT_SCK, 0); 20 | #define TFT_SDA_H digitalWrite(TFT_MOSI, 1); 21 | #define TFT_SDA_L digitalWrite(TFT_MOSI, 0); 22 | 23 | #define TFT_RES_H digitalWrite(TFT_RES, 1); 24 | #define TFT_RES_L digitalWrite(TFT_RES, 0); 25 | #define TFT_DC_H digitalWrite(TFT_DC, 1); 26 | #define TFT_DC_L digitalWrite(TFT_DC, 0); 27 | #define TFT_CS_H digitalWrite(TFT_CS, 1); 28 | #define TFT_CS_L digitalWrite(TFT_CS, 0); 29 | 30 | typedef struct 31 | { 32 | uint8_t cmd; 33 | uint8_t data[4]; 34 | uint8_t len; 35 | } lcd_cmd_t; 36 | 37 | void rm67162_init(void); 38 | 39 | // Set the display window size 40 | void lcd_address_set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); 41 | void lcd_setRotation(uint8_t r); 42 | void lcd_DrawPoint(uint16_t x, uint16_t y, uint16_t color); 43 | void lcd_fill(uint16_t xsta, 44 | uint16_t ysta, 45 | uint16_t xend, 46 | uint16_t yend, 47 | uint16_t color); 48 | void lcd_PushColors(uint16_t x, 49 | uint16_t y, 50 | uint16_t width, 51 | uint16_t high, 52 | uint16_t *data); 53 | void lcd_PushColors(uint16_t *data, uint32_t len); 54 | void lcd_sleep(); 55 | 56 | //nikthefix added functions 57 | void lcd_brightness(uint8_t bright); 58 | void lcd_set_colour_enhance(uint8_t enh); 59 | void lcd_display_off(); 60 | void lcd_display_on(); 61 | void lcd_display_invert_on(); 62 | void lcd_display_invert_off(); 63 | void lcd_display_set_colour_enhance_low_byte(uint8_t ce_low_byte); 64 | void lcd_display_set_colour_enhance_high_byte(uint8_t ce_high_byte); 65 | void lcd_display_high_brightness_mode_on(uint8_t hb_en=0b00000110); 66 | void lcd_display_high_brightness_mode_off(uint8_t hb_en=0b00000100); 67 | -------------------------------------------------------------------------------- /ESP32RemoteForVictron/secret_settings.h: -------------------------------------------------------------------------------- 1 | 2 | #define SECRET_SETTINGS_WIFI_SSID "xxxxx" // your Wi-Fi Station ID which is on the same network as your Victron Venus device 3 | #define SECRET_SETTINGS_WIFI_PASSWORD "yyyyy" // your Wi-Fi Password 4 | 5 | #define SECRET_SETTING_VICTRON_INSTALLATION_ID "+" // your unique Victron Installation/Portal ID. If you don't know it you can use a "+" in this field - but it is more efficient to use the actual ID 6 | #define SECRET_SETTING_VICTRON_MULTIPLUS_ID "+" // your unique Victron Multiplus (vebus) three digit ID (often 288); if you don't know it you can use a "+" in this field but it is more efficient to use the actual ID 7 | #define SECRET_SETTING_VICTRON_SOLAR_CHARGER_ID "+" // your unique Victron Solar Charger three digit ID (often 289); if you don't know it you can use a "+" in this field but it is more efficient to use the actual ID 8 | // note: in any or all of the three cases above if you use a '+' you can look in the Serial Monitor output to see what the discovered IDs are and then come back and change the setting(s) above 9 | 10 | #define SECRET_SETTINGS_MQTT_Broker "venus.local" // generally you can use "venus.local" but this may also be an IPv4 address such as 192.168.1.195 11 | // however, if when you "ping venus.local" you get an IPv6 address, then do not use venus.local but rather the device's IPv4 address 12 | // you can get the device's IPv4 address with the command "ping -4 venus.local" 13 | #define SECRET_SETTINGS_MQTT_UserID "Anonymous" // if you have MQTT security setup, you can enter your MQTT User ID here, otherwise leave the set to "Anonymous" 14 | #define SECRET_SETTINGS_MQTT_Password "Anonymous" // if you have MQTT security setup, you can enter your MQTT Password here, otherwise leave the set to "Anonymous" 15 | #define SECRET_SETTINGS_MQTT_ClientName "ESP32RemoteForVictron" 16 | #define SECRET_SETTINGS_MQTT_Port 1883 17 | 18 | #define SECRET_SETTINGS_OTA_DEVICE_NAME "ESP32RemoteForVictron" // used when GENERAL_SETTINGS_ENABLE_OVER_THE_AIR_UPDATES is set to true 19 | #define SECRET_SETTINGS_OTA_PASSWORD "ESP32RemoteForVictron" // used when GENERAL_SETTINGS_ENABLE_OVER_THE_AIR_UPDATES is set to true 20 | #define SECRET_SETTINGS_OTA_Port 3332 // used when GENERAL_SETTINGS_ENABLE_OVER_THE_AIR_UPDATES is set to true 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Rob Latour 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 Remote for Victron 2 | 3 | Rob Latour, 2025 4 | 5 | ## Project outline 6 | 7 | ESP32 Remote for Victron is an open source project for monitoring and controling a Victron Multiplus II system with a LilyGo T-Display S3 AMOLED. 8 | 9 | ![components](https://github.com/roblatour/ESP32RemoteForVictron/blob/main/images/image01.jpg) 10 | 11 | ## Features 12 | 13 | Monitor: 14 | - Incoming Solar Power 15 | - Incoming Grid Power 16 | - Battery: Percent Charged, Time to Go, Charging/Discharging 17 | - AC Load 18 | - Charger status 19 | - Inverter status 20 | 21 | ![components](https://github.com/roblatour/ESP32RemoteForVictron/blob/main/images/image02.jpg) 22 | 23 | Control: 24 | - Charger on/off 25 | - Inverter on/off 26 | 27 | ![components](https://github.com/roblatour/ESP32RemoteForVictron/blob/main/images/image03.jpg) 28 | 29 | ## License 30 | 31 | MIT 32 | 33 | ## This repo includes 34 | 35 | The open source Arudion code for the ESP32 Remote for Victron project. 36 | 37 | ## LilyGo LilyGo T-Display S3 AMOLED (non touch) 38 | 39 | https://www.lilygo.cc/en-ca/products/t-display-s3-amoled (either the v1 or v2 version) (not an affiliate link) 40 | 41 | ## 3D printable cases 42 | 43 | 3D printable case for a LilyGo T-Display S3 AMOLED v1 (not touch version, no pins): 44 | 45 | - https://github.com/Xinyuan-LilyGO/T-Display-S3-AMOLED/tree/main/3D_File (by LylyGo) 46 | - https://www.printables.com/model/513327 (as pictured above) (by [jeepers01](https://www.printables.com/@jeepers01_100513) ) 47 | - https://www.printables.com/model/566325 (if you don't intend to control the charger/inverter) (by me) 48 | 49 | Filament colour: 50 | 51 | Victron's blue colour: Light Blue 52 | Filament colour code: RAL 5012 https://en.wikipedia.org/wiki/List_of_RAL_colours 53 | 54 | ## Support 55 | 56 | [buy me  a coffee](https://www.buymeacoffee.com/roblatour) 57 | -------------------------------------------------------------------------------- /images/image01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roblatour/ESP32RemoteForVictron/221c71b5dd0ec300fe6d082119ad51ef7c2f3f19/images/image01.jpg -------------------------------------------------------------------------------- /images/image02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roblatour/ESP32RemoteForVictron/221c71b5dd0ec300fe6d082119ad51ef7c2f3f19/images/image02.jpg -------------------------------------------------------------------------------- /images/image03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roblatour/ESP32RemoteForVictron/221c71b5dd0ec300fe6d082119ad51ef7c2f3f19/images/image03.jpg --------------------------------------------------------------------------------