From 2eab71461c902f3370f186d35d1a47d7f750aee2 Mon Sep 17 00:00:00 2001
From: nightflyer88
Date: Wed, 16 Jan 2019 17:29:07 +0100
Subject: [PATCH] ESP8266 support
- add ESP8266 support
- add webpage
---
CG_scale.ino | 650 ++++++++++++++++++++++++++++++++---
data/CG_scale_mechanics.png | Bin 0 -> 327803 bytes
data/battery.png | Bin 0 -> 814 bytes
data/bootstrap.css.map.gz | Bin 0 -> 78255 bytes
data/bootstrap.min.css.gz | Bin 0 -> 20280 bytes
data/bootstrap.min.js.gz | Bin 0 -> 13795 bytes
data/bootstrap.min.js.map.gz | Bin 0 -> 41115 bytes
data/cg.png | Bin 0 -> 2232 bytes
data/cglr.png | Bin 0 -> 2707 bytes
data/settings.png | Bin 0 -> 3121 bytes
data/weight.png | Bin 0 -> 3562 bytes
settings.h | 42 ++-
12 files changed, 640 insertions(+), 52 deletions(-)
create mode 100644 data/CG_scale_mechanics.png
create mode 100644 data/battery.png
create mode 100755 data/bootstrap.css.map.gz
create mode 100755 data/bootstrap.min.css.gz
create mode 100755 data/bootstrap.min.js.gz
create mode 100755 data/bootstrap.min.js.map.gz
create mode 100644 data/cg.png
create mode 100644 data/cglr.png
create mode 100644 data/settings.png
create mode 100644 data/weight.png
diff --git a/CG_scale.ino b/CG_scale.ino
index cc6d543..1758afa 100644
--- a/CG_scale.ino
+++ b/CG_scale.ino
@@ -4,11 +4,12 @@
(c) 2019 by M. Lehmann
------------------------------------------------------------------
*/
-#define CGSCALE_VERSION "1.0"
+#define CGSCALE_VERSION "1.0.51"
/*
******************************************************************
history:
+ V1.1 beta ESP8266
V1.0 12.01.19 first release
@@ -51,6 +52,14 @@
#include
#include
+// libraries for ESP8266 (NodeMCU 1.0 )
+#ifdef ARDUINO_ESP8266_NODEMCU
+#include
+#include
+#include
+#include
+#endif
+
// Settings in separate file
#include "settings.h"
@@ -59,6 +68,12 @@ HX711_ADC LoadCell_1(PIN_LOADCELL1_DOUT, PIN_LOADCELL1_PD_SCK);
HX711_ADC LoadCell_2(PIN_LOADCELL2_DOUT, PIN_LOADCELL2_PD_SCK);
HX711_ADC LoadCell_3(PIN_LOADCELL3_DOUT, PIN_LOADCELL3_PD_SCK);
+// webserver constructor
+#ifdef ARDUINO_ESP8266_NODEMCU
+ESP8266WebServer server(80);
+IPAddress apIP(ip[0], ip[1], ip[2], ip[3]);
+#endif
+
// serial menu
enum
{
@@ -73,12 +88,15 @@ enum
MENU_LOADCELL1_CALIBRATION_FACTOR,
MENU_LOADCELL2_CALIBRATION_FACTOR,
MENU_LOADCELL3_CALIBRATION_FACTOR,
+ MENU_RESISTOR_R1,
+ MENU_RESISTOR_R2,
MENU_BATTERY_MEASUREMENT,
MENU_SHOW_ACTUAL,
MENU_RESET_DEFAULT
};
// EEprom parameter addresses
+#define EEPROM_SIZE 120
enum
{
P_NUMBER_LOADCELLS = 1,
@@ -90,7 +108,9 @@ enum
P_LOADCELL3_CALIBRATION_FACTOR = P_LOADCELL2_CALIBRATION_FACTOR + sizeof(float),
P_ENABLE_BATVOLT = P_LOADCELL3_CALIBRATION_FACTOR + sizeof(float),
P_REF_WEIGHT = P_ENABLE_BATVOLT + sizeof(float),
- P_REF_CG = P_REF_WEIGHT + sizeof(float)
+ P_REF_CG = P_REF_WEIGHT + sizeof(float),
+ P_RESISTOR_R1 = P_REF_CG + sizeof(float),
+ P_RESISTOR_R2 = P_RESISTOR_R1 + sizeof(float)
};
// battery image 12x6
@@ -126,7 +146,7 @@ static const unsigned char CGtransImage[] U8X8_PROGMEM = {
};
// set default text
-static const String PROGMEM newValueText = "Set new value:";
+static const String newValueText = "Set new value:";
// load default values
uint8_t nLoadcells = NUMBER_LOADCELLS;
@@ -136,6 +156,8 @@ float distanceX3 = DISTANCE_X3;
float calFactorLoadcell1 = LOADCELL1_CALIBRATION_FACTOR;
float calFactorLoadcell2 = LOADCELL2_CALIBRATION_FACTOR;
float calFactorLoadcell3 = LOADCELL3_CALIBRATION_FACTOR;
+float resistorR1 = RESISTOR_R1;
+float resistorR2 = RESISTOR_R2;
bool enableBatVolt = ENABLE_VOLTAGE;
float refWeight = REF_WEIGHT;
float refCG = REF_CG;
@@ -147,9 +169,12 @@ float weightLoadCell3 = 0;
float lastWeightLoadCell1 = 0;
float lastWeightLoadCell2 = 0;
float lastWeightLoadCell3 = 0;
+float weightTotal = 0;
+float CG_length = 0;
+float CG_trans = 0;
+float batVolt = 0;
unsigned long lastTimeMenu = 0;
unsigned long lastTimeLoadcell = 0;
-bool displayInit = false;
bool updateMenu = true;
int menuPage = 0;
@@ -158,27 +183,160 @@ int menuPage = 0;
void(* resetCPU) (void) = 0;
-// save calibration factors
+// save values to eeprom
+void saveLoadcells() {
+ EEPROM.put(P_NUMBER_LOADCELLS, nLoadcells);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveDistanceX1() {
+ EEPROM.put(P_DISTANCE_X1, distanceX1);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveDistanceX2() {
+ EEPROM.put(P_DISTANCE_X2, distanceX2);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveDistanceX3() {
+ EEPROM.put(P_DISTANCE_X3, distanceX3);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveRefWeight() {
+ EEPROM.put(P_REF_WEIGHT, refWeight);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveRefCG() {
+ EEPROM.put(P_REF_CG, refCG);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
void saveCalFactor1() {
LoadCell_1.setCalFactor(calFactorLoadcell1);
EEPROM.put(P_LOADCELL1_CALIBRATION_FACTOR, calFactorLoadcell1);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
}
void saveCalFactor2() {
LoadCell_2.setCalFactor(calFactorLoadcell2);
EEPROM.put(P_LOADCELL2_CALIBRATION_FACTOR, calFactorLoadcell2);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
}
void saveCalFactor3() {
LoadCell_3.setCalFactor(calFactorLoadcell3);
EEPROM.put(P_LOADCELL3_CALIBRATION_FACTOR, calFactorLoadcell3);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveResistorR1() {
+ EEPROM.put(P_RESISTOR_R1, resistorR1);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+
+void saveResistorR2() {
+ EEPROM.put(P_RESISTOR_R2, resistorR2);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+void saveEnableBatVolt() {
+ EEPROM.put(P_ENABLE_BATVOLT, enableBatVolt);
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
+}
+
+void auto_calibrate() {
+ Serial.print(F("Autocalibration is running"));
+ for (int i = 0; i <= 20; i++) {
+ Serial.print(F("."));
+ delay(100);
+ }
+ // calculate weight
+ float toWeightLoadCell2 = ((refCG - distanceX1) * refWeight) / distanceX2;
+ float toWeightLoadCell1 = refWeight - toWeightLoadCell2;
+ float toWeightLoadCell3 = 0;
+ if (nLoadcells > 2) {
+ toWeightLoadCell1 = toWeightLoadCell1 / 2;
+ toWeightLoadCell3 = toWeightLoadCell1;
+ }
+ // calculate calibration factors
+ calFactorLoadcell1 = calFactorLoadcell1 / (toWeightLoadCell1 / weightLoadCell1);
+ calFactorLoadcell2 = calFactorLoadcell2 / (toWeightLoadCell2 / weightLoadCell2);
+ if (nLoadcells > 2) {
+ calFactorLoadcell3 = calFactorLoadcell3 / (toWeightLoadCell3 / weightLoadCell3);
+ }
+ saveCalFactor1();
+ saveCalFactor2();
+ saveCalFactor3();
+ // finish
+ Serial.println(F("done"));
}
void setup() {
+#ifdef ARDUINO_ESP8266_NODEMCU
+ // init webserver
+ WiFi.mode(WIFI_AP);
+ WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
+ WiFi.softAP(ssid, password);
+
+ server.begin();
+ server.on("/", main_page);
+ server.on("/index.html", main_page);
+ server.on("/settings", settings_page);
+ server.on("/settings.png", settingsImg);
+ server.on("/weight.png", weightImg);
+ server.on("/cg.png", cgImg);
+ server.on("/cglr.png", cgLRimg);
+ server.on("/battery.png", batteryImg);
+ server.on("/CG_scale_mechanics.png", mechanicsImg);
+ server.on("/bootstrap.min.css", bootstrap);
+ server.on("bootstrap.min.css", bootstrap);
+ server.on("/popper.min.js", popper);
+ server.on("/bootstrap.min.js", bootstrapmin);
+ server.on("bootstrap.min.js", bootstrapmin);
+
+ SPIFFS.begin();
+ EEPROM.begin(EEPROM_SIZE);
+#endif
+
// init OLED display
oledDisplay.begin();
oledDisplay.firstPage();
@@ -237,25 +395,44 @@ void setup() {
EEPROM.get(P_REF_CG, refCG);
}
+ if (EEPROM.read(P_RESISTOR_R1) != 0xFF) {
+ EEPROM.get(P_RESISTOR_R1, resistorR1);
+ }
+
+ if (EEPROM.read(P_RESISTOR_R2) != 0xFF) {
+ EEPROM.get(P_RESISTOR_R2, resistorR2);
+ }
+
// init Loadcells
LoadCell_1.begin();
LoadCell_2.begin();
- LoadCell_3.begin();
+ if (nLoadcells > 2) {
+ LoadCell_3.begin();
+ }
// tare
- while (!LoadCell_1.startMultiple(STABILISINGTIME) && !LoadCell_2.startMultiple(STABILISINGTIME) && !LoadCell_3.startMultiple(STABILISINGTIME)) {
+ if (nLoadcells > 2) {
+ while (!LoadCell_1.startMultiple(STABILISINGTIME) && !LoadCell_2.startMultiple(STABILISINGTIME) && !LoadCell_3.startMultiple(STABILISINGTIME)) {
+ }
+ } else {
+ while (!LoadCell_1.startMultiple(STABILISINGTIME) && !LoadCell_2.startMultiple(STABILISINGTIME)) {
+ }
}
// set calibration factor
LoadCell_1.setCalFactor(calFactorLoadcell1);
LoadCell_2.setCalFactor(calFactorLoadcell2);
- LoadCell_3.setCalFactor(calFactorLoadcell3);
+ if (nLoadcells > 2) {
+ LoadCell_3.setCalFactor(calFactorLoadcell3);
+ }
// stabilize scale values
for (int i = 0; i <= 5; i++) {
LoadCell_1.update();
LoadCell_2.update();
- LoadCell_3.update();
+ if (nLoadcells > 2) {
+ LoadCell_3.update();
+ }
delay(200);
}
@@ -269,7 +446,9 @@ void loop() {
LoadCell_1.update();
LoadCell_2.update();
- LoadCell_3.update();
+ if (nLoadcells > 2) {
+ LoadCell_3.update();
+ }
// update loadcell values
@@ -299,11 +478,6 @@ void loop() {
if ((millis() - lastTimeMenu) > UPDATE_INTERVAL_OLED_MENU) {
lastTimeMenu = millis();
- float weightTotal;
- float CG_length = 0;
- float CG_trans = 0;
- float batVolt = 0;
-
// total model weight
weightTotal = weightLoadCell1 + weightLoadCell2 + weightLoadCell3;
if (weightTotal < MINIMAL_TOTAL_WEIGHT && weightTotal > MINIMAL_TOTAL_WEIGHT * -1) {
@@ -318,11 +492,14 @@ void loop() {
if (nLoadcells > 2) {
CG_trans = (distanceX3 / 2) - (((weightLoadCell1 + weightLoadCell2 / 2) * distanceX3) / weightTotal);
}
+ }else{
+ CG_length = 0;
+ CG_trans = 0;
}
// read battery voltage
if (enableBatVolt) {
- batVolt = (analogRead(VOLTAGE_PIN) / 1024.0) * V_REF * (float(RESISTOR_R1 + RESISTOR_R2) / RESISTOR_R2) / 1000.0;
+ batVolt = (analogRead(VOLTAGE_PIN) / 1024.0) * V_REF * ((resistorR1 + resistorR2) / resistorR2) / 1000.0;
}
// print to display
@@ -389,66 +566,43 @@ void loop() {
break;
case MENU_LOADCELLS:
nLoadcells = Serial.parseInt();
- EEPROM.put(P_NUMBER_LOADCELLS, nLoadcells);
+ saveLoadcells();
menuPage = 0;
updateMenu = true;
break;
case MENU_DISTANCE_X1:
distanceX1 = Serial.parseFloat();
- EEPROM.put(P_DISTANCE_X1, distanceX1);
+ saveDistanceX1();
menuPage = 0;
updateMenu = true;
break;
case MENU_DISTANCE_X2:
distanceX2 = Serial.parseFloat();
- EEPROM.put(P_DISTANCE_X2, distanceX2);
+ saveDistanceX2();
menuPage = 0;
updateMenu = true;
break;
case MENU_DISTANCE_X3:
distanceX3 = Serial.parseFloat();
- EEPROM.put(P_DISTANCE_X3, distanceX3);
+ saveDistanceX3();
menuPage = 0;
updateMenu = true;
break;
case MENU_REF_WEIGHT:
refWeight = Serial.parseFloat();
- EEPROM.put(P_REF_WEIGHT, refWeight);
+ saveRefWeight();
menuPage = 0;
updateMenu = true;
break;
case MENU_REF_CG:
refCG = Serial.parseFloat();
- EEPROM.put(P_REF_CG, refCG);
+ saveRefCG();
menuPage = 0;
updateMenu = true;
break;
case MENU_AUTO_CALIBRATE:
if (Serial.read() == 'J') {
- Serial.print(F("Autocalibration is running"));
- for (int i = 0; i <= 20; i++) {
- Serial.print(F("."));
- delay(100);
- }
- // calculate weight
- float toWeightLoadCell2 = ((refCG - distanceX1) * refWeight) / distanceX2;
- float toWeightLoadCell1 = refWeight - toWeightLoadCell2;
- float toWeightLoadCell3 = 0;
- if (nLoadcells > 2) {
- toWeightLoadCell1 = toWeightLoadCell1 / 2;
- toWeightLoadCell3 = toWeightLoadCell1;
- }
- // calculate calibration factors
- calFactorLoadcell1 = calFactorLoadcell1 / (toWeightLoadCell1 / weightLoadCell1);
- calFactorLoadcell2 = calFactorLoadcell2 / (toWeightLoadCell2 / weightLoadCell2);
- if (nLoadcells > 2) {
- calFactorLoadcell3 = calFactorLoadcell3 / (toWeightLoadCell3 / weightLoadCell3);
- }
- saveCalFactor1();
- saveCalFactor2();
- saveCalFactor3();
- // finish
- Serial.println(F("done"));
+ auto_calibrate();
menuPage = 0;
updateMenu = true;
}
@@ -471,13 +625,25 @@ void loop() {
menuPage = 0;
updateMenu = true;
break;
+ case MENU_RESISTOR_R1:
+ resistorR1 = Serial.parseFloat();
+ saveResistorR1();
+ menuPage = 0;
+ updateMenu = true;
+ break;
+ case MENU_RESISTOR_R2:
+ resistorR2 = Serial.parseFloat();
+ saveResistorR2();
+ menuPage = 0;
+ updateMenu = true;
+ break;
case MENU_BATTERY_MEASUREMENT:
if (Serial.read() == 'J') {
enableBatVolt = true;
} else {
enableBatVolt = false;
}
- EEPROM.put(P_ENABLE_BATVOLT, enableBatVolt);
+ saveEnableBatVolt();
menuPage = 0;
updateMenu = true;
break;
@@ -490,10 +656,13 @@ void loop() {
//chr = Serial.read();
if (Serial.read() == 'J') {
// reset eeprom
- for (int i = 0; i < 100; i++) {
+ for (int i = 0; i < EEPROM_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
Serial.end();
+#ifdef ARDUINO_ESP8266_NODEMCU
+ EEPROM.commit();
+#endif
resetCPU();
}
menuPage = 0;
@@ -530,13 +699,17 @@ void loop() {
Serial.print(calFactorLoadcell2);
Serial.print(F(")\n10 - Set calibration factor of load cell 3 ("));
Serial.print(calFactorLoadcell3);
- Serial.print(F(")\n11 - Enable battery voltage measurement ("));
+ Serial.print(F(")\n11 - Set value of resistor R1 ("));
+ Serial.print(resistorR1);
+ Serial.print(F("ohm)\n12 - Set value of resistor R2 ("));
+ Serial.print(resistorR2);
+ Serial.print(F("ohm)\n13 - Enable battery voltage measurement ("));
if (enableBatVolt) {
Serial.print(F("enabled)\n"));
} else {
Serial.print(F("disabled)\n"));
}
- Serial.print(F("12 - Show actual values\n13 - Reset to factory defaults\n\n"));
+ Serial.print(F("14 - Show actual values\n15 - Reset to factory defaults\n\n"));
Serial.print(F("Please choose the menu number:"));
updateMenu = false;
break;
@@ -603,6 +776,18 @@ void loop() {
Serial.print(newValueText);
updateMenu = false;
break;
+ case MENU_RESISTOR_R1:
+ Serial.print(F("\n\nValue of resistor R1: "));
+ Serial.println(resistorR1);
+ Serial.print(newValueText);
+ updateMenu = false;
+ break;
+ case MENU_RESISTOR_R2:
+ Serial.print(F("\n\nValue of resistor R2: "));
+ Serial.println(resistorR2);
+ Serial.print(newValueText);
+ updateMenu = false;
+ break;
case MENU_BATTERY_MEASUREMENT:
Serial.print(F("\n\nEnable battery voltage measurement (J/N)?\n"));
updateMenu = false;
@@ -642,4 +827,369 @@ void loop() {
updateMenu = true;
}
}
+
+#ifdef ARDUINO_ESP8266_NODEMCU
+ server.handleClient();
+#endif
+
}
+
+
+#ifdef ARDUINO_ESP8266_NODEMCU
+void main_page()
+{
+ char buff[8];
+ String webPage = "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "CG scale by M. Lehmann";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "
";
+ webPage += "";
+ webPage += "";
+
+ // print weight
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ dtostrf(weightTotal, 5, 1, buff);
+ webPage += buff;
+ webPage += "g
";
+ webPage += "
";
+ webPage += "
";
+
+ // print cg
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ dtostrf(CG_length, 5, 1, buff);
+ webPage += buff;
+ webPage += "mm
";
+ webPage += "
";
+ webPage += "
";
+
+ // print cg trans
+ if (nLoadcells > 2) {
+ //webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ dtostrf(CG_trans, 5, 1, buff);
+ webPage += buff;
+ webPage += "mm
";
+ webPage += "
";
+ webPage += "
";
+
+ }
+
+ // print battery
+ if (enableBatVolt) {
+ //webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += "
";
+ webPage += batVolt;
+ webPage += "V
";
+ webPage += "
";
+ webPage += "
";
+ }
+ webPage += "
";
+ webPage += "";
+ webPage += "(c) 2019 M. Lehmann - Version: ";
+ webPage += CGSCALE_VERSION;
+ webPage += "
";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+
+ server.send(200, "text/html", webPage);
+
+}
+
+void settings_page()
+{
+ if ( server.hasArg("nLoadcells")) {
+ nLoadcells = server.arg("nLoadcells").toFloat();
+ saveLoadcells();
+ }
+ if ( server.hasArg("distanceX1")) {
+ distanceX1 = server.arg("distanceX1").toFloat();
+ saveDistanceX1();
+ }
+ if ( server.hasArg("distanceX2")) {
+ distanceX2 = server.arg("distanceX2").toFloat();
+ saveDistanceX2();
+ }
+ if ( server.hasArg("distanceX3")) {
+ distanceX3 = server.arg("distanceX3").toFloat();
+ saveDistanceX3();
+ }
+ if ( server.hasArg("refWeight")) {
+ refWeight = server.arg("refWeight").toFloat();
+ saveRefWeight();
+ }
+ if ( server.hasArg("refCG")) {
+ refCG = server.arg("refCG").toFloat();
+ saveRefCG();
+ }
+ if ( server.hasArg("calFactorLoadcell1")) {
+ calFactorLoadcell1 = server.arg("calFactorLoadcell1").toFloat();
+ saveCalFactor1();
+ }
+ if ( server.hasArg("calFactorLoadcell2")) {
+ calFactorLoadcell2 = server.arg("calFactorLoadcell2").toFloat();
+ saveCalFactor2();
+ }
+ if ( server.hasArg("calFactorLoadcell3")) {
+ calFactorLoadcell3 = server.arg("calFactorLoadcell3").toFloat();
+ saveCalFactor3();
+ }
+ if ( server.hasArg("resistorR1")) {
+ resistorR1 = server.arg("resistorR1").toFloat();
+ saveResistorR1();
+ }
+ if ( server.hasArg("resistorR2")) {
+ resistorR2 = server.arg("resistorR2").toFloat();
+ saveResistorR2();
+ }
+ if ( server.hasArg("enableBatVolt")) {
+ if (server.arg("enableBatVolt") == "ON") {
+ enableBatVolt = true;
+ } else {
+ enableBatVolt = false;
+ }
+ }
+ if ( server.hasArg("calibrate")) {
+ auto_calibrate();
+ }
+
+
+ String webPage = "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "CG scale by M. Lehmann";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "
";
+ webPage += "";
+
+ webPage += "";
+
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "
";
+
+ webPage += "
";
+
+ webPage += "";
+ webPage += "(c) 2019 M. Lehmann - Version: ";
+ webPage += CGSCALE_VERSION;
+ webPage += "";
+ webPage += "";
+ webPage += "";
+ webPage += "";
+
+ server.send(200, "text/html", webPage);
+}
+
+void settingsImg()
+{
+ File file = SPIFFS.open("/settings.png", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void weightImg()
+{
+ File file = SPIFFS.open("/weight.png", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void cgImg()
+{
+ File file = SPIFFS.open("/cg.png", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void cgLRimg()
+{
+ File file = SPIFFS.open("/cglr.png", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void batteryImg()
+{
+ File file = SPIFFS.open("/battery.png", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void mechanicsImg()
+{
+ File file = SPIFFS.open("/CG_scale_mechanics.png", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void bootstrap()
+{
+ File file = SPIFFS.open("/bootstrap.min.css.gz", "r");
+ size_t sent = server.streamFile(file, "text/css");
+}
+
+void popper()
+{
+ File file = SPIFFS.open("/popper.min.js.gz", "r");
+ size_t sent = server.streamFile(file, "application/javascript");
+}
+
+void bootstrapmin()
+{
+ File file = SPIFFS.open("/bootstrap.min.js.gz", "r");
+ size_t sent = server.streamFile(file, "application/javascript");
+}
+#endif
diff --git a/data/CG_scale_mechanics.png b/data/CG_scale_mechanics.png
new file mode 100644
index 0000000000000000000000000000000000000000..249ca6255fef9ab8dfee990572d5a67c2762784a
GIT binary patch
literal 327803
zcmeFaXH=9~vo?%~iV6rSK~a)oAfp13QA9^jg3#n73?Mm4PGSHB0TZAgK@ov&nw}
zL;;Z;n;;-L=g`o6bSSGw;9mU5g*C)k|Br@4a_bU3FE}wVx@<-=I8j`~V3F
z3FXb-uH7La*`ER*)5-V3-(1YC$|ND#b=Fi`TG3qkhP1VXwVkrfT|=WQzgrs^-MMj<
zUs(7u3CX#ahWh$OuBBERKg>
zaKCy(Y0OKK>i>X>EG_#%`zZC&{*>e71S<90zrDElVq@3WAvx0YcT$eCvC{3NEP~Ct
z)YSKHRVm1#NLF=A>l+)dzP=_&O~#%;%g)j%Toj#l?xJ|iwJ$UbcM=Xnx>UJ}9Js#M
zCxn6`+Qv7Yc!Hct;d<&ta(*(Yr?oHj^=I^|i;L&>u^Knl6sJh}dV70uT)kEM!uyI;
zl=t1<_dgpyUWn&^eyf6l;_(WJ&`()o=35R2VdeJQ{H9?~Lc((j`LoM8R??A#M2h6*
zwJR#lyC%B#y|F;O6Z~;IaQ9&qyYE-3ev_9DB;Eb=_ZOGSSg9!3_)hQILw4n54PO~e
z8RxMpigxm^>;u_^eZ%HkHq{im^Bm+IdK_ju%ooSdua_hgCMCMfyO%xP5;YrMU4KBb
ziEzaMs&
zp#A^$$bY_88aa82(cQN5|MFGg+wXS&+xL7SBlUp$V7~nRfBB|T4{A^R@2mY^kM7My
zN@}6e?jZYb-;~s1=-=)q`R_E;`ZXm<%KvWr$*HOJFYfzuMgNx%EdCEjwCEfo4*$zH
zeLxZ}{BJj&RD+DHJuPMY&A)w9vi5KPcH^nPvauA0Jkg2!x4REVoa$e0{DYk%zQvC1
z9PzEr=FSn{!T@)?_!eGw$BS>_es{e17IAk6sBD2Sc7Vzj&~gW;>;RQ5R$vFHY_$SA
zKxK;+*a0eAtiTRX*;RQ5R$vFHY_$SAKxK;+*a0eAtiTRX*KD32wLFf(+*m|psK5pzwmgxE$Q+-v}sAs1tB
zv$BE?bFbL^x7Q)sc2$>V%Z`{Fi~3i+#CsK_9$3zB={%EaJ0M0}88H3%_2pu+=v2Tw;v
z)baak%KPNsnRk;p;w`gXN7A|#P2S%1x!?cnY!==K&$s_4(Y?oOb0cFi3m?)mSh|Tn$LC85FV>0KYub?QH&Y)q|W3O5e7%<|NW
z>wfl#A9wT2yVI6M8%v`zrH2&YKe^;vSrEd=zX=b&A5DmhG;3UZEP*cLYD@uW^
zfbAQqH4AT*e}CwmNyy|>_2lf?PWbBU>(3cpTH#iQ76wE0R0Hi{d2jDKd0(;9WjI1v
zEZ;Y8koe7ajacz`ymESI_aU*A`|;U5uI7bkv(a6f4@B2QtqE>Dk}RoOXSM}p3mW<(
zn-1x9(srb~NO#jdmA&SB(oub_oYzxHkLf;38=>Eyx3QdF?|o9iT=o4jnj4>BEiVRK
z{C;_N?(fIH&3df#`Hud4t&kKn3r7s=JGuAL<+Ddv_$EkcnneblUz8;T#0IG8IJ7iw
z=vAJ~?)7x*^jPk0f+g%8$Ga`IsErSorO)IH1j>ykrpqVSIk0R60sabrcKgXw24A)D
zdb1vOp3H1(guQ;r5(|?y*Ve
zP4=B24oDJX^bG9}?;>>}`U-kVQm@yhZb0P~c*7AvZ?IcV%hYnZ4XR{;-nz^--EhY)
z+sdKax<|I~VSnvu^f+{?=S`jBXCd7<;tWT#F|CFtJkn#SgQydI$VA7!R=_m7+i|?~
z*m=s~6@$?Na_@Wa3mIOMF}aR>$e8~w8kdY)GpqMG+0rF1xv_91Kk^{WKmJDjls#Om
zBJCfOZm>L(dVDgg%dTdjg3)>*Z#j23QndC$4VUTKXq-Z-yB^tDCYPTtb=6cE`3%YYC-?EtM3>cFx80UH&66`$`u*c%=yfto
zF`<7Qo2|BOgbF4>rdCcoe^~s0EKqpp?ZU9+X6}GM#W9oj8HB0)IB-Ec+&MoU#++on
zl3v4GRDdHJ9te_@=p?pMre5ksW0JzoL^}7k>ckWv6BlY9cYx(03F~kAwxsrptx0bu?6$UAJ3ua18=WB%&Fv&Pq
zO?~e@jR$!P6eZ7w$On?xX%N7hHpz=lzL~-irqZM>&i=LQTjUf{g1h9N8ppKpdw-#E
z2AA6S!I!CX*5Jr3zAJgKE=_e_#QChg%$(wO!yuhxG+j%(!LjeD6~{4ic}laB1@qrA
zV#a)x^e3@7{eGjcpSR*795s!KQs?1aWgCiMh{xNCer1r?OGCJfJw*K@Qx_Pr$9Ub0
zvqT-l7SmEnpZuebiuLd}Dq_?(vq9$GZ6lu9%)ZCVo>
zA`*~1N9qXEAo>hs#I?4f&)`(C8F8?=r*vEfgH2!EJY(wiZDk-RN?vTX?9z0>d{%dF
z`(?YIUn}H#|9P^eUVt=&dNM4iM2dVthlL}0W7%udY4F)=c@a~Oj#_osq~ys5yQ$*U
z_>hA>nm|+V)Xy^Yy^cBK*lhWok6vp{QDd|nngpwYqKUK`AMrBB&5h+aB#aiqu^ol@
zo$OTzr*1U7cilM5vD0*1uj#gsj>l??wa2(tzz3@8Wd(`V)7>_ul#6_~7EVTe@n!Z@
zPj0>hE-$XbIGnqDd388S5($Wp8cno+)(Z6|E)yVrep1u($Q(9D>_k~!*r-nJ+v^rp
zEpD0kG%d~N;vr!H>+T^Y+q?SRsWHqv=^fkYODDJ=h8kg&QDsB()kzl_
z+MarL>csJS2r}c>^H!OTCmVX@8+HbKu$8yQ6)pF;jXD((bn@Us6Bwt~q1_4sEh?s9
zgEeLjLj+)UN2;0ZxtdBmBQf}+i?VyGy!P`8yt~}|KNRcAG
zxjsKS?X~G9F4d9~_hE(DD976pstn=Me%u?^vp^F()v93TOIBoOC@~%y6
z&2(R0Lv#?`7`Y)i=CoNjiWz$gQR)-!(-eTDFx=W01})umRQc9RK>%DwuYAlM3d78`
zdrye{d?7s+W!NF$*kKgfY?@GYS;*ZQ07h2mvpP}FGa|LwZ{^GrQ?KLA%PLMfwzEdq
zRvZ&248OPk-pI}|9Bz$U06?>Rf{_(N9=MoQZbB<}B0EcRz3JbO^svU#UJU%?3J3i3JIQ4;9)V`mdkvh(OPe#9<3QAnx
zpsDj+(GVGWYuK1Ko}}d|W*Ds5tcKHAOgAxw
zlcS@>Z|u6q7-6|+(*qk%-jXa4Ua6$=aD35&zUExWlGLZe)$F2={)rd@rK)dtf+NaE@pwZK#gh9_!>?jouve^z*vXL{9zntm673FQeEH1`Qh3
z-{!wvtb4HU7%GIxYkjtH{F*>=tDyFPzY$-|$D1@=_fB4|BYe^3NFV;VxhbPk+G&~H
z-N|F!?rPCOFo|?IgH0(pFbcp#L8%HY#4Yh!z4Jnd#DujQVKTd^&zD&@Ihsx`i0(>6
zV5kU0j7CAcg|ePi>cE$iR8P^`a$Kp`@5bQ_H>e-#Y2
z{Cf*)0=Z|%R1k+1CNWKRlxWpWLyK$GG{-J{*~h$K`ZSfoT1D=;+3VExDgEpfVJ`DR
z52mtJrMuX%^dO_>`PlNq&Ewz<9z+`2D$LG1L$(zg{{uy);^q-99A(0P$p43~pX@^q
zOYpaz8_V)swqo@m$kudq6Sb|L+q3lA6R~_RY(+!2AS(Y0qw{H##w1q?Xp4{gl6ddh
zzYe%ylpzek_xqY}4E)?fr_Mr4`&zarM8~nC;)8gvuf2Qfb8RxSYMjA-+=qK{k*;QI
zZC)E~fl<=dgg_SE$L)Zoo(0BrLH=j@&M4%V%}7jx5}CchyAod9%E#1%$>k;mNpVXE
z4kv@m-R!C=Alt#cC{Q
zXV3#}jc|y_HI4yUg-yeqJQiY|GCsD_N)DXdnay_|EuG**KW%xGg6gXo!q=>dxxuLi
zOhkO#c6=G!2>pWD&Ay7+N!;+7P9sCj+c{X?Y)NE!>!W5xe79ZR7t&IDvQCVxo#rDa
zEx+J=GC6I1uEt5sIG$=`X_j&U*XuT2c)xhpJ{8^1>Q@D@0OP>H8l!zbf4rcYdf%|n
zK)d4^&52IKAn`k7pQ<%gCLt8uzC&iWHyzS3lqp25Tc@r+zvtEwZVlp}*Y4xmHC2pOId>sndaap7?8WDVuDq|-wDw#=g1RfYZI>;6^Oz0f^uanf
zocKe1Lruot6yt4^l@`(JX{i#~jQG6+ZYnIC2c_xcE-&Z#8@y|(HYD76mpFIzQOZU1
z{fhLD$t^P?4ku|xz!1}q_T`{upX5fF++cF~-4)`5Xt#XjMtNGo$)NM1EN#=TDx51m
zLG1tLu@O0M{R9`-0-5~ZQntSb1Ml~wlbeGg!%r7(Kaff$6WNGyP&pOuK3C=Dfc3_I3M!U
z-{v+KhTc0CFj3AzK|IsZngpt$v{yphwtwC0pE+llI)2R%vf8BKXLp^#bAgQk4$hAIa#H6<*++sy`Ld4=FYA6Onh6+KV2dCSv=Hj
zpkwOodoRum#vse>cRh(V-js~|`AgDrNwYS;eR88}H1SogdfgH@@BRsRk=P{&kn&h^
zDU_pUgT@byAk*5o
zr2^u7)TVYg*feLd@?)ZF;B*MRjDC(P*C9>eDIGw$^}hJtI4o4t-0A82K>$NI}pPUq|s6*w`p^|jnCNmxL$M7Nn*o88lb{aX^>w7S1CM5?r
z6JLm+hWvzEZgZn6WfqIu^t=l1tK0TQOkoy&vbNg-*QTZ`5JJFeUKf%NjOvzOR%um0
za-gYr*E8kzY2?YV@%J=ZKhEi|uhX$+rAdoe)qyGO75pp_D*7=?n5C`vfTfSYk!>lq
zXbjLgoh{5kUue$Jj~somCbFm9paX}txt`m&i9h1~;ER=u3n#>vwK^`Jetqjg=aPEbrSIp>k0^-Qt|f1-C99|$&BS5p}+2$FRG>?x#
zR_zM8;B`Dbqd`Igc?qSeztIuHV?WZQ^tfhS!0`vQ>To5u;~i_bvp;>D)9{Nu5U!tL{2$kH*%&>PGi_eY6CCkfLYl2hN+cj+~!HLQ76JDHU?Q9vx#+!o}PaoPsz=BNlQXL5My^9
zD~45W0x_Ji7{Fqnvgf(1PA&KMqVc5sJE^~0%nw0WPE+NIdgm9*QRC>exD+p;POE7~
zA;Gb~!b2BQ^MKni?m<$Pk5fMS86?rwc}r6XpQe?gR8zhFd{#!$AvRdGTA;aMjk4`+
zkdY-!D
z>E?5zTOK^wB1=Oafkm_i+7OKGtbK+Vw)Zt{E
z^e_U61wD3L0__Rq7v3Z2UR*x~AsXZ9E&+E`J$MY0)M4uKbp(@b2^dUPL2UNgXd6^G
z+VFA?Vmu`hN=l=my{qzOlobwQE+qR35Vm9qZ_QhHmCNM#m9QR9EbKsRn=A1D92ouJ
zNcGehvxM(;5(KWy@4kTZggoGY$LR2*g|T^K=orf&%2bNG_Wt@RDB5f^zkpsGquvem
z44oGl-Rg!_{pgVN16q&i={4_txDSEci6Z%=P4TYaD=lcZ6KD^ba~LxZm%wrdwi(q-
zGMhLex}wplS1QT`_@&?TYg)5`kI+W&_xbMLyi@4?NZiJTi{h+395W*8L+hfGi{vU6
zhV3w@xMg9Y>d2)tgzb-T_qk|NEg?^iifhsIq)HC4=s~I!2f_+^!ZqC*h;EGUZ&O8^
z#6aI0QXC;^{>j(<2MgI$GN6R>NuB2cTEf8$AxTGBQ*Y^;vtt_{`Rd=`l34|W6iYsd
z#IMEaSqU^{A#BCTM}pFPpVk_^m4Xu!WoFoMdokkcGo_{gIvEpdS~yc=*t6nST9V4|
zL@C8$8>%0gD0y&_N))ZuS|9gaat|WGU)guEd0v9!rw1&V__UVZ^Qv80@*gsW+$L3w
zlF)|@<{kFVJA4~dzcBaKFfbuepU&fY%BWh=8mbd2KYQ)TAK78iBx+neg;iSU0
ztklR}geYA(`rKPel1yYznJ%!IYm$d6(_Dl#vxBUo&mg5XKaz&I8eY9aR*Q?SG;>J#
zWlV4O*v5>nWFqhW%Rm%{>Y~xE8qPQF8C}-m+U?YWQi~OxF1U!0xWP5yRKq#PJy0$J
zfycg0(|lw2Mxw>FkFli*sr+P>TIh_WC!!nuIFsHQ1j>9(RmB9@VaXF^)XxstXS{7P
zvP5ctsQL7X0;`Um&_P!)w>hh0jvgmyT>VV0ttUE|(ct`Jtp+-p1AyitXpN?@Jyk{B
z#!i%tehyE%RNRUmF18V?GvJ!W5v)2aKaIvk;qL2o1meDpR|ueQ`K&qJMD+&DQVuPD
z@#I-{u=p1%<}FqgVwGcW$inAy(0w_-_cpz@&6QCdcVN>-;m6C{*NHog@)vf~1Vta}=NI^X#z#$W7
zL2@bE>uSY~=xh2A8+khBy}}Xpf1xRgxUea>Fpp9(^7FGYvINk3jJ<%al5%Lp7(gjf
zQwep@OJ;F^*9n8p{^V($EO;={?MX`xR~4o{Px@LPz7xu
zrAO^b=!?hEefUZ#*dBdJ5#?Z=YMl@pg5G!Y55CKns+vgdKxxA0M?`9D0NF)@d5#_1
zRSozvPhztN%B2lfLg-`xO)eDvxW2zf>hpVK?QH5QnWv}TGU<;i7$y7Y1WwZ=b%M(t
z_im8isGN&W{r%pzP%CRJiX^-e&+~gL@b(TZE)9D5BwB&P8gbR0{GV}I=)uc1Nx}0@
zRf=L~Yo;z{9dhzAGCT%^R&|BG==Lxxd=J#?R-5<*Noh&_xzc{~^P))B4CD%l7h9;8
zZUPu}$2RtReo_%}t@_arRJR(I4h^qNLC$$J_>&!+6bIt!a9e1@b6ItowS(E@WQziC
zsA<2mWjHk}-3YLgQ$i(JtQMjl^qr$WRvZrOORQR7CNQ~YYxyfrASH0L8p(U0eaC8!
zRm{DH6b^Mfi9}1KFu{yw?Hy1s7^=}G928*^6)Km9eyHEE1+w_I(w*SW7%xsaL7+d0xQr_bknNajSd
zV2|^F>x_fn`OhQw@2O1$Mb!Xtik(zPn3ycQsAZxEZ)EsniNA293BupwW8E2-+oCu=
zqLzDdtj3rB`;4mA;R=``p08bCs@z`mD-X2Nuv!xP2UzNFc$4p!xy;Vt8Jrpa^{AZk
z6wt&*zW2MByc3|fiNR|9tOcSl1c`t5
z%W2geLXM?iLqFwZAqA;P{x_^Pb98!V?E+Vx2s<>n}rVM#;Fv=l-~hZ7+cP0y8R
zqdcauvKk>L2f}Dp5GUD!kqh3_9>$sSM_Geo5z4kEFe^K^-S4UNH2K_MiNphe;F!1As>Lb#d}i?G*@!oe0G+D`@+?hS>INZo(M^8a!egWA
zz>M0}@)W6d@SUrLa)hufNlGRZOfw@T
zwWl#9hBPbBIJ{_HSXe5pPT_lrj&(!vWh;f#9?8QV12QUvJm9T3c3T*(&3y=Pn_QZn
zb-7NGq3mo4r3bX{-O7v^&6OJt6=`&JP=t0%M4pBwjVe2hE|^&hDKfQeRMy#Xoy3PO
zA?1(3nYC01-tIb)H6GWeZA$ck$@{>WzdZ=jInPqu8qDl}g=Qq87%BlFY+n^yJ{qV!
zeuEA<&Mhj*aY1HJHkxju4uY}ep<`pSGI2_?<3O!h;x`p7pBF1|;ajIL9KX>FJo^EJ
z--#DUN&TqYGgPL!Uq-F~I#0mb+|vVm<)tF5`v&BrpP(To9oKBiCEc+mFqzS`^nMdU
zN~`o8aYjvDuEK@J^A7_HO^xKDmxj7k`rWVY
z365w4epx1~(XE_6n6j}_DakY`LJuDF9{xDvM|6)O_ig-0Ow)3F#)^YAbkd;wOyp2n
z7OCZ9ax|559}La>qpFY+{=({!dEpR+Koe7IO)^`I=6pI=V9kb@e_e{1@DNmf*%2-c
zmED{^jZ~dnrwf-|dzP1Ekm~5H2OQ-9!g)d@Ff$AaWItdL2h%>pp8x!ot=#KE{dRom
zF4Ar`fcM$%hbyi`N0EkGq}?35{oveHF{SgEOthMLcIsoi(v6eBA?gOt7NFxHV~S~T
z0|^i!+($&(2mE(k8%&I6QKlsxH;R6=ekMeU(>%3n41BI?mfM?2<=X-+=ccuKzkwTz
zu?Pbun`%!T=|Qd+NXjhXI-%ZF93uRnZUJ?fr;E!DKpuIv!OYT|6Dmv$nEpd
zr0uknz9QMj3yr|l>*>iZOlJ2c6&OF33IQv<)TV788z9tkxfWpJc_ORmRdbpH{>exJ
zAP(^1{Cw7_
z%YOA)9~0_BXQujLYltJ(+2-|7gr>gxbVYdNwtuYhsdLSpY!(c=%HxaM!s&v^v^dol
zhYG;MdFP|)7`2l+RN)0?_^l8zY|!M6k#x*}1e@KHV7XO%x0!q%Jv>3nu!5qYxA
zTVYS0HN4$AkLmM8#F=#|bSwk^{3f8v@gI1H1I?F6$#8U}P#>5!D}Q9%j%(HP+K|Yl
zTwV#|KJ!-pp+rEY&^~K$Nx`i$PjZlwPV3Sg8NLsyDpD9RFL=^;08wg|b$W&Y0vvS#
zZoFvY7C&}Fa~aw~&D7s@vE+pY_C6uTtMk{f-aYqCg($~*x`yyKeLCKY}ZtT+<>ew
zlaczQs?vy|rHvdTXcEjly`T~u)He9Pczxl_SiuTyMf0q%+(o}LuBAx(*H*PlD
zkq8;=$vSTI(~zF4Q^OtmnBD7VHBLm?rZ&S;Jk`vHN`!fxrqLWW$~N|4&>KLkmTBwdHZl?DL#qBR@w@{#@k1
zxnuFlJfH4ajlLGW7$A}>Z}ld?@8iqMvAF@XR{O^;8^`1fzljN?J7fIPU@G^`;{!bB
z*&ZJBBV${iUKQUYEa5^4JsUl}ZoTuub@LL-*^z_xQ-%8OqW`02sPgy$ZM)g#69ra9
zN>W?@FP-|bSq5^JGYQhZN3-bzFu2T5_pfjE%c=)pxmsTl1s5%}j}1e+DQErxEz5eg
z=f?#&@MD*f1dn8E1!%RE(`NKnoBPxAT4g-TiO|nQC49aePM6i|Nl;{5;?P2h4#)cA
z*x8dPj;ftDzh~Z*L#tBws}cr5#gxxA?brXy#=
z%3jXIiWy>8xDS-7aScf=KK#<_&A~acC^cT&n!fKnn%RfH_4~QT+z6tYHA&WfCt$An
zG%6%yMRWq8*a(_EMRonvKC^W%Ag{n!Fb@PEjT#+s4ZfW=7lS0_#`3hdi$cdMl&8!i
zz0}UNoo(A5$pxQdDOP=!{!xKU^SomI*d02e@K}7z)k(AjHZgX#%}gz?I?K?)%}0i3AM|!|I^}D$89uby+sCO2>v^zk1E!3@f>
zuS@u|4l-Ow(y2|>77BL|C%#+um0bUzQP=8q4i?gnph#I1>R;JZZwm`~FJ3Zf`zBHj
zls;B=@~tNgOTEY+wyH-^FS1o>qWvH$C<}_-U%iUPuP5OM%V{Kt(wa!Z-
z5snykQq#W|55!9v$<;}^SZ>eku7d*=0SA@iT;9idjMo`ewNU#2OZ`
z--+p;JcIJqrU%ysOh?4(ia0MdGq-IIy#6|fo#&`GZHWMBlyjgzp!C76-JR-MUscm`
zApn=$7V19Q-)dsXJ=s!})2O0-a~R;#w}*oKKbkZ+Q(BHqK+x@wwXme5sG#mznH?wv
z>Q~uuygpBG9EY^oYDCXKvP;qMQNRdlFI0!b7Zh`TzLE=b7xyVLC{7Y!Ps&=r=xuP^
ziWJLjXoCTSj_cFegT|NqILpe5Z)iRAf|#X6r;9i6=k9EbP%Xs2=N4zgOJpQ93|XA&
ze4jNoW*(Gmi?7TvxXY9@*|6E#ntHF{V=GRRcG?n(5FOL04AB9sZLM!s@+Ex?fl{nW
z0dhwG<0re~LFdI=!D6?5vZwKIX*xm^Khl!O&PYO5GRLcB#x2)bY%p+sJ^yO1M6Ud{
z#A^I$?^bpu4pXwVs}H0ek=hc9a%4Jy-h2P#H8OZW_hh#L3rkYqkw@l$`|qrDw&7a!
zMX`nLA;j_*(iA_+xLK1Sy3{A@66)8Tb6f2~%G|yT>mvP6Pswr}A1{knib-o3Fs=;?#$Io3PcL6i0vh4l
zM)B@LTsZ9|XV{IJhi~AQ$v(w+me1$7YUCcyzCJl}IHKQOW;sVw!(O&4gpWue21rK3
z*|O{@O|1*IJL(ypTQD$}TXts508)Pp5F@MOLt;+<5TIrzVDjG0q(^OVA)cLk%`xF|uZiu%tdlcEOnFrI
zXClSZx+wTcN7e`a^)}tu^i8gW-sz-uw_Kunb;R(h(-S)nyNGQYT@=rgcB{69$c{eu
zq-Lp4CFMUiTcbz3us$2)MPXgE;kcxcqJKp_*UpHJf#K3UMLHh8yV)y;Mr|G5e&-ro
z%QkC4bG@Z5-EX^?m1Nf{PlWW$FbDe&+*VcdoT(t
z!G5=jgM-kb!1uvZL1M2gYv_jst*TZ51{rHDl%x8`By5xLn#OgJCnJHr;Ir;2px$jJ
z7s6H5`D_49C_`m(6qe;a2S^vI+dMj7`A9e(sM^#U!L+Rk+dOtm&%p#)e(@JK~mAhG%P83cRv=^7-yF0Vp
zmZt(JV|At+hN9Rh9WFoVt#Kjhd|ZhxURemxjF$?gAi9e?vGJFL$3xNe8l`6paQ
zy~FDK>Sum&K|6HjKj1pD9Xj)$a_t=+e|w+219kq%C+|R=9jNo);BJS`{3jRte;1v(
z*KnTvKUx4gRordKGLS>tNpycfnZE+i9q+&6{dc_ouQsujx%^*O%#vCbWBj!2RuJ>W-$Lf+>7cQ!*Lzk8_v@n%^4!1gvDA#)Ir8?0*
zW!ai+Gujy22ilYNZ=oBjHK^R5-uKI+eZF@~eV%-f_BGxZZHT$C(I&F64%3^I(1>{+
ze|?jAY?c@fNuG8BDQyo+^eyp<*C7Rs(SH6eV
z`GGX>m!Q;iTE^~DG4QcifaaaX42NeoH1J=IlJL;f(YsOP3}Rf5&ntg^463c?q0x=;
zZVMWv;f2h$+Q7tugZeywh9k@a{2q1eqVZ#fNKQ2aMRS;WJAh~=me3cil-mVmuB^=s
z)|7(cN6ie(!(Qrw;r%`spy1KEL9svy9iQ&=;k^`yvJ~AMC&5Ovn?cmkqBYsBrJVRJ
zTqwEAvW=1E9!IpW=lTkt!&vimGaLVH&d+_FU7RTn-MLNn|4B}~$;c3C1sm9dZ2WJ&
z&`%29&a@Omx126Xhe?8&g6h3$bq)G%PU|FM^&+!sCyzw2k6yg{4wqh
z*WQ6YXa^~vbVW_u4O_Psm{MXtm_@4>RLTFFsu~Z->LEtJXH@-$I{OHgb5?9`Pk&bA
z?O>yt5CH~VEt3cb#HNF>BdyTj3dM;x0GIJxc
zRH)SiIC=OVv?%puwtvsD9cU#_E^ldNf~NDmrRL=x{+0aKoMANcch=N3yfM;9TYG@a
z6VwQF3