";
diff --git a/printermonitor/printermonitor.ino.bak b/printermonitor/printermonitor.ino.bak
new file mode 100644
index 0000000..77db194
--- /dev/null
+++ b/printermonitor/printermonitor.ino.bak
@@ -0,0 +1,1376 @@
+/** The MIT License (MIT)
+
+Copyright (c) 2018 David Payne
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+// Additional Contributions:
+/* 15 Jan 2019 : Owen Carter : Add psucontrol option and processing */
+/* 18 Feb 2022 : Robert von Könemann @vknmnn : Lets us select Moonraker + fix usage of AMPM/24Hrs time */
+/* 17 Sep 2025 : Eduardo Romero @eduardorq : Fix Weather API request data and Spanish translation */
+ /**********************************************
+ * Edit Settings.h for personalization
+ ***********************************************/
+
+#include "Settings.h"
+
+#define VERSION "3.1"
+
+#define HOSTNAME "PrintMon-"
+#define CONFIG "/conf.txt"
+
+/* Useful Constants */
+#define SECS_PER_MIN (60UL)
+#define SECS_PER_HOUR (3600UL)
+
+/* Useful Macros for getting elapsed time */
+#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)
+#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN)
+#define numberOfHours(_time_) (_time_ / SECS_PER_HOUR)
+
+// Initialize the oled display for I2C_DISPLAY_ADDRESS
+// SDA_PIN and SCL_PIN
+#if defined(DISPLAY_SH1106)
+ SH1106Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN);
+#else
+ SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN); // this is the default
+#endif
+
+OLEDDisplayUi ui( &display );
+
+void drawProgress(OLEDDisplay *display, int percentage, String label);
+void drawOtaProgress(unsigned int, unsigned int);
+void drawScreen1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
+void drawScreen2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
+void drawScreen3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
+void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
+void drawClock(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
+void drawWeather(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y);
+void drawClockHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state);
+
+// Set the number of Frames supported
+const int numberOfFrames = 3;
+FrameCallback frames[numberOfFrames];
+FrameCallback clockFrame[2];
+boolean isClockOn = false;
+
+OverlayCallback overlays[] = { drawHeaderOverlay };
+OverlayCallback clockOverlay[] = { drawClockHeaderOverlay };
+int numberOfOverlays = 1;
+
+// Time
+TimeClient timeClient(UtcOffset);
+long lastEpoch = 0;
+long firstEpoch = 0;
+long displayOffEpoch = 0;
+String lastMinute = "xx";
+String lastSecond = "xx";
+String lastReportStatus = "";
+boolean displayOn = true;
+
+// Printer Client
+#if defined(USE_REPETIER_CLIENT)
+ RepetierClient printerClient(PrinterApiKey, PrinterServer, PrinterPort, PrinterAuthUser, PrinterAuthPass, HAS_PSU);
+#elif defined(USE_MOONRAKER_CLIENT)
+ MoonrakerClient printerClient(PrinterApiKey, PrinterServer, PrinterPort, PrinterAuthUser, PrinterAuthPass, HAS_PSU);
+#else
+ OctoPrintClient printerClient(PrinterApiKey, PrinterServer, PrinterPort, PrinterAuthUser, PrinterAuthPass, HAS_PSU);
+#endif
+int printerCount = 0;
+
+// Weather Client
+OpenWeatherMapClient weatherClient(WeatherApiKey, CityIDs, 1, IS_METRIC, WeatherLanguage);
+
+//declairing prototypes
+void configModeCallback (WiFiManager *myWiFiManager);
+int8_t getWifiQuality();
+
+ESP8266WebServer server(WEBSERVER_PORT);
+ESP8266HTTPUpdateServer serverUpdater;
+
+static const char WEB_ACTIONS[] PROGMEM = " Inicio"
+ " Configuración"
+ " Clima"
+ " Restablecer configuración"
+ " Olvidar WiFi"
+ " Actualizar Firmware"
+ " Sobre";
+
+String CHANGE_FORM = ""; // moved to config to make it dynamic
+
+static const char CLOCK_FORM[] PROGMEM = "
Mostrar reloj cuando la impresora esté apagada
"
+ "
Usar reloj 24 Horas
"
+ "
Voltear la orientación de la pantalla
"
+ "
Activar LED de WiFi
"
+ "
Utilizar complemento de control OctoPrint PSU para reloj/blanco
"
+ "
Actualizar datos del clima
";
+
+static const char THEME_FORM[] PROGMEM = "
Color de plantilla
"
+ "
"
+ "
Utilice credenciales de seguridad para cambios de configuración
"
+ "
"
+ "
"
+ "
";
+
+static const char WEATHER_FORM[] PROGMEM = "
"
+ "";
+
+static const char LANG_OPTIONS[] PROGMEM = "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
";
+
+static const char COLOR_THEMES[] PROGMEM = "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
"
+ "
";
+
+
+void setup() {
+ Serial.begin(115200);
+ SPIFFS.begin();
+ delay(10);
+
+ //New Line to clear from start garbage
+ Serial.println();
+
+ // Initialize digital pin for LED (little blue light on the Wemos D1 Mini)
+ pinMode(externalLight, OUTPUT);
+
+ //Some Defaults before loading from Config.txt
+ PrinterPort = printerClient.getPrinterPort();
+
+ readSettings();
+
+ // initialize display
+ display.init();
+ if (INVERT_DISPLAY) {
+ display.flipScreenVertically(); // connections at top of OLED display
+ }
+ display.clear();
+ display.display();
+
+ //display.flipScreenVertically();
+
+ display.setTextAlignment(TEXT_ALIGN_CENTER);
+ display.setContrast(255); // default is 255
+ display.setFont(ArialMT_Plain_16);
+ display.drawString(64, 1, "Printer Monitor");
+ display.setFont(ArialMT_Plain_10);
+ display.drawString(64, 18, "for " + printerClient.getPrinterType());
+ display.setFont(ArialMT_Plain_16);
+ display.drawString(64, 30, "By Qrome");
+ display.drawString(64, 46, "V" + String(VERSION));
+ display.display();
+
+ //WiFiManager
+ //Local intialization. Once its business is done, there is no need to keep it around
+ WiFiManager wifiManager;
+
+ // Uncomment for testing wifi manager
+ //wifiManager.resetSettings();
+ wifiManager.setAPCallback(configModeCallback);
+
+ String hostname(HOSTNAME);
+ hostname += String(ESP.getChipId(), HEX);
+ if (!wifiManager.autoConnect((const char *)hostname.c_str())) {// new addition
+ delay(3000);
+ WiFi.disconnect(true);
+ ESP.reset();
+ delay(5000);
+ }
+
+ // You can change the transition that is used
+ // SLIDE_LEFT, SLIDE_RIGHT, SLIDE_TOP, SLIDE_DOWN
+ ui.setFrameAnimation(SLIDE_LEFT);
+ ui.setTargetFPS(30);
+ ui.disableAllIndicators();
+ ui.setFrames(frames, (numberOfFrames));
+ frames[0] = drawScreen1;
+ frames[1] = drawScreen2;
+ frames[2] = drawScreen3;
+ clockFrame[0] = drawClock;
+ clockFrame[1] = drawWeather;
+ ui.setOverlays(overlays, numberOfOverlays);
+
+ // Inital UI takes care of initalising the display too.
+ ui.init();
+ if (INVERT_DISPLAY) {
+ display.flipScreenVertically(); //connections at top of OLED display
+ }
+
+ // print the received signal strength:
+ Serial.print("Intensidad de la señal (RSSI): ");
+ Serial.print(getWifiQuality());
+ Serial.println("%");
+
+ if (ENABLE_OTA) {
+ ArduinoOTA.onStart([]() {
+ Serial.println("Start");
+ });
+ ArduinoOTA.onEnd([]() {
+ Serial.println("\nEnd");
+ });
+ ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
+ Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
+ });
+ ArduinoOTA.onError([](ota_error_t error) {
+ Serial.printf("Error[%u]: ", error);
+ if (error == OTA_AUTH_ERROR) Serial.println("Autentificación fallida");
+ else if (error == OTA_BEGIN_ERROR) Serial.println("Inicio fallido");
+ else if (error == OTA_CONNECT_ERROR) Serial.println("Conexión fallida");
+ else if (error == OTA_RECEIVE_ERROR) Serial.println("Recepción fallida");
+ else if (error == OTA_END_ERROR) Serial.println("Finalización fallida");
+ });
+ ArduinoOTA.setHostname((const char *)hostname.c_str());
+ if (OTA_Password != "") {
+ ArduinoOTA.setPassword(((const char *)OTA_Password.c_str()));
+ }
+ ArduinoOTA.begin();
+ }
+
+ if (WEBSERVER_ENABLED) {
+ server.on("/", displayPrinterStatus);
+ server.on("/systemreset", handleSystemReset);
+ server.on("/forgetwifi", handleWifiReset);
+ server.on("/updateconfig", handleUpdateConfig);
+ server.on("/updateweatherconfig", handleUpdateWeather);
+ server.on("/configure", handleConfigure);
+ server.on("/configureweather", handleWeatherConfigure);
+ server.onNotFound(redirectHome);
+ serverUpdater.setup(&server, "/update", www_username, www_password);
+ // Start the server
+ server.begin();
+ Serial.println("Servidor iniciado");
+ // Print the IP address
+ String webAddress = "http://" + WiFi.localIP().toString() + ":" + String(WEBSERVER_PORT) + "/";
+ Serial.println("Use esta URL : " + webAddress);
+ display.clear();
+ display.setTextAlignment(TEXT_ALIGN_CENTER);
+ display.setFont(ArialMT_Plain_10);
+ display.drawString(64, 10, "Entorno web iniciado");
+ display.drawString(64, 20, "Debe conectar a la IP");
+ display.setFont(ArialMT_Plain_16);
+ display.drawString(64, 30, WiFi.localIP().toString());
+ display.drawString(64, 46, "Puerto: " + String(WEBSERVER_PORT));
+ display.display();
+ } else {
+ Serial.println("Entorno web Deshabilitado");
+ display.clear();
+ display.setTextAlignment(TEXT_ALIGN_CENTER);
+ display.setFont(ArialMT_Plain_10);
+ display.drawString(64, 10, "Entorno web deshabilitado");
+ display.drawString(64, 20, "Activado en Settings.h");
+ display.display();
+ }
+ flashLED(5, 100);
+ findMDNS(); //go find Printer Server by the hostname
+ Serial.println("*** Leaving setup()");
+}
+
+void findMDNS() {
+ if (PrinterHostName == "" || ENABLE_OTA == false) {
+ return; // nothing to do here
+ }
+ // We now query our network for 'web servers' service
+ // over tcp, and get the number of available devices
+ int n = MDNS.queryService("http", "tcp");
+ if (n == 0) {
+ Serial.println("sin servicio - compruebe que el servidor de la impresora esté encendido");
+ return;
+ }
+ Serial.println("*** Buscando " + PrinterHostName + " en mDNS");
+ for (int i = 0; i < n; ++i) {
+ // Going through every available service,
+ // we're searching for the one whose hostname
+ // matches what we want, and then get its IP
+ Serial.println("Found: " + MDNS.hostname(i));
+ if (MDNS.hostname(i) == PrinterHostName) {
+ IPAddress serverIp = MDNS.IP(i);
+ PrinterServer = serverIp.toString();
+ PrinterPort = MDNS.port(i); // save the port
+ Serial.println("*** Servidor de impresora encontrado " + PrinterHostName + " http://" + PrinterServer + ":" + PrinterPort);
+ writeSettings(); // update the settings
+ }
+ }
+}
+
+//************************************************************
+// Main Loop
+//************************************************************
+void loop() {
+
+ //Get Time Update
+ if((getMinutesFromLastRefresh() >= minutesBetweenDataRefresh) || lastEpoch == 0) {
+ getUpdateTime();
+ }
+
+ if (lastMinute != timeClient.getMinutes() && !printerClient.isPrinting()) {
+ // Check status every 60 seconds
+ ledOnOff(true);
+ lastMinute = timeClient.getMinutes(); // reset the check value
+ printerClient.getPrinterJobResults();
+ printerClient.getPrinterPsuState();
+ ledOnOff(false);
+ } else if (printerClient.isPrinting()) {
+ if (lastSecond != timeClient.getSeconds() && timeClient.getSeconds().endsWith("0")) {
+ lastSecond = timeClient.getSeconds();
+ // every 10 seconds while printing get an update
+ ledOnOff(true);
+ printerClient.getPrinterJobResults();
+ printerClient.getPrinterPsuState();
+ ledOnOff(false);
+ }
+ }
+
+ checkDisplay(); // Check to see if the printer is on or offline and change display.
+
+ ui.update();
+
+ if (WEBSERVER_ENABLED) {
+ server.handleClient();
+ }
+ if (ENABLE_OTA) {
+ ArduinoOTA.handle();
+ }
+}
+
+void getUpdateTime() {
+ ledOnOff(true); // turn on the LED
+ Serial.println();
+
+ if (displayOn && DISPLAYWEATHER) {
+ Serial.println("Obteniendo datos del clima...");
+ weatherClient.updateWeather();
+ }
+
+ Serial.println("Actualizando...");
+ //Update the Time
+ timeClient.updateTime();
+ lastEpoch = timeClient.getCurrentEpoch();
+
+ if (IS_24HOUR) {
+ Serial.println("Local time: " + timeClient.getFormattedTime());
+ } else {
+ Serial.println("Local time: " + timeClient.getAmPmFormattedTime());
+ }
+
+ ledOnOff(false); // turn off the LED
+}
+
+boolean authentication() {
+ if (IS_BASIC_AUTH && (strlen(www_username) >= 1 && strlen(www_password) >= 1)) {
+ return server.authenticate(www_username, www_password);
+ }
+ return true; // Authentication not required
+}
+
+void handleSystemReset() {
+ if (!authentication()) {
+ return server.requestAuthentication();
+ }
+ Serial.println("Reiniciar configuración del sistema");
+ if (SPIFFS.remove(CONFIG)) {
+ redirectHome();
+ ESP.restart();
+ }
+}
+
+void handleUpdateWeather() {
+ if (!authentication()) {
+ return server.requestAuthentication();
+ }
+ DISPLAYWEATHER = server.hasArg("isWeatherEnabled");
+ WeatherApiKey = server.arg("openWeatherMapApiKey");
+ CityIDs[0] = server.arg("city1").toInt();
+ IS_METRIC = server.hasArg("metric");
+ WeatherLanguage = server.arg("language");
+ writeSettings();
+ isClockOn = false; // this will force a check for the display
+ checkDisplay();
+ lastEpoch = 0;
+ redirectHome();
+}
+
+void handleUpdateConfig() {
+ boolean flipOld = INVERT_DISPLAY;
+ if (!authentication()) {
+ return server.requestAuthentication();
+ }
+ if (server.hasArg("printer")) {
+ printerClient.setPrinterName(server.arg("printer"));
+ }
+ PrinterApiKey = server.arg("PrinterApiKey");
+ PrinterHostName = server.arg("PrinterHostName");
+ PrinterServer = server.arg("PrinterAddress");
+ PrinterPort = server.arg("PrinterPort").toInt();
+ PrinterAuthUser = server.arg("octoUser");
+ PrinterAuthPass = server.arg("octoPass");
+ DISPLAYCLOCK = server.hasArg("isClockEnabled");
+ IS_24HOUR = server.hasArg("is24hour");
+ INVERT_DISPLAY = server.hasArg("invDisp");
+ USE_FLASH = server.hasArg("useFlash");
+ HAS_PSU = server.hasArg("hasPSU");
+ minutesBetweenDataRefresh = server.arg("refresh").toInt();
+ themeColor = server.arg("theme");
+ UtcOffset = server.arg("utcoffset").toFloat();
+ String temp = server.arg("userid");
+ temp.toCharArray(www_username, sizeof(temp));
+ temp = server.arg("stationpassword");
+ temp.toCharArray(www_password, sizeof(temp));
+ writeSettings();
+ findMDNS();
+ printerClient.getPrinterJobResults();
+ printerClient.getPrinterPsuState();
+ if (INVERT_DISPLAY != flipOld) {
+ ui.init();
+ if(INVERT_DISPLAY)
+ display.flipScreenVertically();
+ ui.update();
+ }
+ checkDisplay();
+ lastEpoch = 0;
+ redirectHome();
+}
+
+void handleWifiReset() {
+ if (!authentication()) {
+ return server.requestAuthentication();
+ }
+ //WiFiManager
+ //Local intialization. Once its business is done, there is no need to keep it around
+ redirectHome();
+ WiFiManager wifiManager;
+ wifiManager.resetSettings();
+ ESP.restart();
+}
+
+void handleWeatherConfigure() {
+ if (!authentication()) {
+ return server.requestAuthentication();
+ }
+ ledOnOff(true);
+ String html = "";
+
+ server.sendHeader("Cache-Control", "no-cache, no-store");
+ server.sendHeader("Pragma", "no-cache");
+ server.sendHeader("Expires", "-1");
+ server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+ server.send(200, "text/html", "");
+
+ html = getHeader();
+ server.sendContent(html);
+
+ String form = FPSTR(WEATHER_FORM);
+ String isWeatherChecked = "";
+ if (DISPLAYWEATHER) {
+ isWeatherChecked = "checked='checked'";
+ }
+ form.replace("%IS_WEATHER_CHECKED%", isWeatherChecked);
+ form.replace("%WEATHERKEY%", WeatherApiKey);
+ form.replace("%CITYNAME1%", weatherClient.getCity(0));
+ form.replace("%CITY1%", String(CityIDs[0]));
+ String checked = "";
+ if (IS_METRIC) {
+ checked = "checked='checked'";
+ }
+ form.replace("%METRIC%", checked);
+ String options = FPSTR(LANG_OPTIONS);
+ options.replace(">"+String(WeatherLanguage)+"<", " selected>"+String(WeatherLanguage)+"<");
+ form.replace("%LANGUAGEOPTIONS%", options);
+ server.sendContent(form);
+
+ html = getFooter();
+ server.sendContent(html);
+ server.sendContent("");
+ server.client().stop();
+ ledOnOff(false);
+}
+
+void handleConfigure() {
+ if (!authentication()) {
+ return server.requestAuthentication();
+ }
+ ledOnOff(true);
+ String html = "";
+
+ server.sendHeader("Cache-Control", "no-cache, no-store");
+ server.sendHeader("Pragma", "no-cache");
+ server.sendHeader("Expires", "-1");
+ server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+ server.send(200, "text/html", "");
+
+ html = getHeader();
+ server.sendContent(html);
+
+ CHANGE_FORM = "