diff --git a/README.md b/README.md new file mode 100644 index 0000000..22bd953 --- /dev/null +++ b/README.md @@ -0,0 +1,96 @@ +/* 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. +*/ + +# Printer Monitor (OctoPrint 3D Printer Monitor) + +## Features: +* Displays the print status from OctoPrint Server +* Estimated time remaining +* Time Printing +* Percentage complete +* Progress bar +* Screen turns off when printer is turned off or disconnected +* Screen turns on when printer is Operational or connected +* Sample rate is every 60 seconds when not printing +* Sample rate is every 10 seconds when printing +* Fully configurable from the web interface (not required to update Settings.h) +* Supports OTA (loading firmware over WiFi connection on same LAN) +* Basic Athentication to protect your settings + +## Required Parts: +* Wemos D1 Mini: https://amzn.to/2qLyKJd +* 0.96" OLED I2C 128x64 Display (12864) SSD1306: https://amzn.to/2JDEAUF + +## Wiring for the Wemos D1 Mini to the I2C SSD1306 OLED +SDA -> D2 +SCL -> D5 +VCC -> 5V+ +GND -> GND- + +## 3D Printed Case by Qrome: +https://www.thingiverse.com/thing:2884823 + +## Compiling and Loading to Wemos D1 Mini +It is recommended to use Arduino IDE. You will need to configure Arduino IDE to work with the Wemos board and USB port and installed the required USB drivers etc. +* USB CH340G drivers: https://wiki.wemos.cc/downloads +* Enter http://arduino.esp8266.com/stable/package_esp8266com_index.json into Additional Board Manager URLs field. You can add multiple URLs, separating them with commas. This will add support for the Wemos D1 Mini to Arduino IDE. +* Open Boards Manager from Tools > Board menu and install esp8266 platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation). +* Select Board: "WeMos D1 R2 & mini" + +**Packages** -- the following packages and libraries are used (download and install): +ESP8266WiFi.h +ESP8266WebServer.h +ArduinoJson.h --> https://github.com/bblanchon/ArduinoJson +WiFiManager.h --> https://github.com/tzapu/WiFiManager +ESP8266mDNS.h +ArduinoOTA.h --> Arduino OTA Library +"SSD1306Wire.h" --> https://github.com/ThingPulse/esp8266-oled-ssd1306 +"OLEDDisplayUi.h" + +## Initial Configuration +You will can update the **Settings.h** file with your OctoPrint API Key or do it from the web interface. +* Your OctoPrint API Key from your OctoPrint -> User Settings -> Current API Key + +NOTE: The settings in the Settings.h are the default settings for the first loading. After loading you will manage changes to the settings via the Web Interface. If you want to change settings again in the settings.h, you will need to erase the file system on the Wemos or use the “Reset Settings” option in the Web Interface. + +## Web Interface +The Marquee Scroller uses the **WiFiManager** so when it can't find the last network it was connected to +it will become a **AP Hotspot** -- connect to it with your phone and you can then enter your WiFi connection information. + +After connected to your WiFi network it will display the IP addressed assigned to it and that can be +used to open a browser to the Web Interface. Everything can be configured there. + +

+ + + + +

+ +## Contributors +David Payne +Daniel Eichhorn -- Author of the TimeClient class and OLEDDisplayUi + +![Printer Monitor Temps](/images/temperatures.jpg) +![Printer Monitor Time Remaining](/images/time_remaining.jpg) +![Printer Monitor Printing Time](/images/printing_time.jpg) \ No newline at end of file diff --git a/images/Thumbs.db b/images/Thumbs.db new file mode 100644 index 0000000..3e48389 Binary files /dev/null and b/images/Thumbs.db differ diff --git a/images/printing_time.jpg b/images/printing_time.jpg new file mode 100644 index 0000000..eccaa48 Binary files /dev/null and b/images/printing_time.jpg differ diff --git a/images/shot_01.png b/images/shot_01.png new file mode 100644 index 0000000..7a51b35 Binary files /dev/null and b/images/shot_01.png differ diff --git a/images/shot_02.png b/images/shot_02.png new file mode 100644 index 0000000..a028a0f Binary files /dev/null and b/images/shot_02.png differ diff --git a/images/shot_03.png b/images/shot_03.png new file mode 100644 index 0000000..3da6fbb Binary files /dev/null and b/images/shot_03.png differ diff --git a/images/shot_04.png b/images/shot_04.png new file mode 100644 index 0000000..960354b Binary files /dev/null and b/images/shot_04.png differ diff --git a/images/temperatures.jpg b/images/temperatures.jpg new file mode 100644 index 0000000..8fd1f25 Binary files /dev/null and b/images/temperatures.jpg differ diff --git a/images/time_remaining.jpg b/images/time_remaining.jpg new file mode 100644 index 0000000..18b6d69 Binary files /dev/null and b/images/time_remaining.jpg differ diff --git a/printermonitor/OctoPrintClient.cpp b/printermonitor/OctoPrintClient.cpp new file mode 100644 index 0000000..2db4341 --- /dev/null +++ b/printermonitor/OctoPrintClient.cpp @@ -0,0 +1,254 @@ +/** 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. +*/ + +#include "OctoPrintClient.h" + +OctoPrintClient::OctoPrintClient(String ApiKey, String server, int port) { + updateOctoPrintClient(ApiKey, server, port); +} + +void OctoPrintClient::updateOctoPrintClient(String ApiKey, String server, int port) { + server.toCharArray(myServer, 100); + myApiKey = ApiKey; + myPort = port; +} + +boolean OctoPrintClient::validate() { + boolean rtnValue = false; + printerData.error = ""; + if (String(myServer) == "") { + printerData.error += "Server address is required; "; + } + if (myApiKey == "") { + printerData.error += "ApiKey is required; "; + } + if (printerData.error == "") { + rtnValue = true; + } + return rtnValue; +} + +WiFiClient OctoPrintClient::getSubmitRequest(String apiGetData) { + WiFiClient printClient; + printClient.setTimeout(5000); + + Serial.println("Getting Octoprint Data"); + Serial.println(apiGetData); + result = ""; + if (printClient.connect(myServer, myPort)) { //starts client connection, checks for connection + printClient.println(apiGetData); + printClient.println("Host: " + String(myServer) + ":" + String(myPort)); + printClient.println("X-Api-Key: " + myApiKey); + printClient.println("User-Agent: ArduinoWiFi/1.1"); + printClient.println("Connection: close"); + if (printClient.println() == 0) { + Serial.println("Connection to " + String(myServer) + ":" + String(myPort) + " failed."); + Serial.println(); + printerData.error = "Connection to " + String(myServer) + ":" + String(myPort) + " failed."; + printerData.state = ""; + return printClient; + } + } + else { + Serial.println("Connection to OctoPrint failed: " + String(myServer) + ":" + String(myPort)); //error message if no client connect + Serial.println(); + printerData.error = "Connection to OctoPrint failed: " + String(myServer) + ":" + String(myPort); + printerData.state = ""; + return printClient; + } + + // Check HTTP status + char status[32] = {0}; + printClient.readBytesUntil('\r', status, sizeof(status)); + if (strcmp(status, "HTTP/1.1 200 OK") != 0 && strcmp(status, "HTTP/1.1 409 CONFLICT") != 0) { + Serial.print(F("Unexpected response: ")); + Serial.println(status); + printerData.state = ""; + printerData.error = "Response: " + String(status); + return printClient; + } + + // Skip HTTP headers + char endOfHeaders[] = "\r\n\r\n"; + if (!printClient.find(endOfHeaders)) { + Serial.println(F("Invalid response")); + printerData.error = "Invalid response from " + String(myServer) + ":" + String(myPort); + printerData.state = ""; + } + + return printClient; +} + +void OctoPrintClient::getPrinterJobResults() { + if (!validate()) { + return; + } + String apiGetData = "GET /api/job HTTP/1.1"; + + WiFiClient printClient = getSubmitRequest(apiGetData); + + if (printerData.error != "") { + return; + } + + const size_t bufferSize = JSON_OBJECT_SIZE(1) + JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(3) + 2*JSON_OBJECT_SIZE(5) + JSON_OBJECT_SIZE(6) + 710; + DynamicJsonBuffer jsonBuffer(bufferSize); + + // Parse JSON object + JsonObject& root = jsonBuffer.parseObject(printClient); + if (!root.success()) { + Serial.println("OctoPrint Data Parsing failed: " + String(myServer) + ":" + String(myPort)); + printerData.error = "OctoPrint Data Parsing failed: " + String(myServer) + ":" + String(myPort); + printerData.state = ""; + return; + } + + printerData.averagePrintTime = (const char*)root["job"]["averagePrintTime"]; + printerData.estimatedPrintTime = (const char*)root["job"]["estimatedPrintTime"]; + printerData.fileName = (const char*)root["job"]["file"]["name"]; + printerData.fileSize = (const char*)root["job"]["file"]["size"]; + printerData.lastPrintTime = (const char*)root["job"]["lastPrintTime"]; + printerData.progressCompletion = (const char*)root["progress"]["completion"]; + printerData.progressFilepos = (const char*)root["progress"]["filepos"]; + printerData.progressPrintTime = (const char*)root["progress"]["printTime"]; + printerData.progressPrintTimeLeft = (const char*)root["progress"]["printTimeLeft"]; + printerData.filamentLength = (const char*)root["job"]["filament"]["tool0"]["length"]; + printerData.state = (const char*)root["state"]; + + if (isOperational()) { + Serial.println("Status: " + printerData.state); + } else { + Serial.println("Printer Not Opperational"); + } + + //**** get the Printer Temps and Stat + apiGetData = "GET /api/printer?exclude=sd,history HTTP/1.1"; + printClient = getSubmitRequest(apiGetData); + if (printerData.error != "") { + return; + } + const size_t bufferSize2 = 3*JSON_OBJECT_SIZE(2) + 2*JSON_OBJECT_SIZE(3) + JSON_OBJECT_SIZE(9) + 300; + DynamicJsonBuffer jsonBuffer2(bufferSize2); + + // Parse JSON object + JsonObject& root2 = jsonBuffer2.parseObject(printClient); + if (!root2.success()) { + printerData.isPrinting = false; + printerData.toolTemp = ""; + printerData.toolTargetTemp = ""; + printerData.bedTemp = ""; + printerData.bedTargetTemp = (const char*)root2["temperature"]["bed"]["target"]; + return; + } + + String printing = (const char*)root2["state"]["flags"]["printing"]; + if (printing == "true") { + printerData.isPrinting = true; + } + printerData.toolTemp = (const char*)root2["temperature"]["tool0"]["actual"]; + printerData.toolTargetTemp = (const char*)root2["temperature"]["tool0"]["target"]; + printerData.bedTemp = (const char*)root2["temperature"]["bed"]["actual"]; + printerData.bedTargetTemp = (const char*)root2["temperature"]["bed"]["target"]; + + if (isPrinting()) { + Serial.println("Status: " + printerData.state + " " + printerData.fileName + "(" + printerData.progressCompletion + "%)"); + } + + printClient.stop(); //stop client +} + +String OctoPrintClient::getAveragePrintTime(){ + return printerData.averagePrintTime; +} + +String OctoPrintClient::getEstimatedPrintTime() { + return printerData.estimatedPrintTime; +} + +String OctoPrintClient::getFileName() { + return printerData.fileName; +} + +String OctoPrintClient::getFileSize() { + return printerData.fileSize; +} + +String OctoPrintClient::getLastPrintTime(){ + return printerData.lastPrintTime; +} + +String OctoPrintClient::getProgressCompletion() { + return String(printerData.progressCompletion.toInt()); +} + +String OctoPrintClient::getProgressFilepos() { + return printerData.progressFilepos; +} + +String OctoPrintClient::getProgressPrintTime() { + return printerData.progressPrintTime; +} + +String OctoPrintClient::getProgressPrintTimeLeft() { + return printerData.progressPrintTimeLeft; +} + +String OctoPrintClient::getState() { + return printerData.state; +} + +boolean OctoPrintClient::isPrinting() { + return printerData.isPrinting; +} + +boolean OctoPrintClient::isOperational() { + boolean operational = false; + if (printerData.state == "Operational" || isPrinting()) { + operational = true; + } + return operational; +} + +String OctoPrintClient::getTempBedActual() { + return printerData.bedTemp; +} + +String OctoPrintClient::getTempBedTarget() { + return printerData.bedTargetTemp; +} + +String OctoPrintClient::getTempToolActual() { + return printerData.toolTemp; +} + +String OctoPrintClient::getTempToolTarget() { + return printerData.toolTargetTemp; +} + +String OctoPrintClient::getFilamentLength() { + return printerData.filamentLength; +} + +String OctoPrintClient::getError() { + return printerData.error; +} diff --git a/printermonitor/OctoPrintClient.h b/printermonitor/OctoPrintClient.h new file mode 100644 index 0000000..8c694e5 --- /dev/null +++ b/printermonitor/OctoPrintClient.h @@ -0,0 +1,87 @@ +/** 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. +*/ + +#pragma once +#include +#include + +class OctoPrintClient { + +private: + char myServer[100]; + int myPort = 80; + String myApiKey = ""; + + boolean validate(); + WiFiClient getSubmitRequest(String apiGetData); + + String result; + + typedef struct { + String averagePrintTime; + String estimatedPrintTime; + String fileName; + String fileSize; + String lastPrintTime; + String progressCompletion; + String progressFilepos; + String progressPrintTime; + String progressPrintTimeLeft; + String state; + String toolTemp; + String toolTargetTemp; + String filamentLength; + String bedTemp; + String bedTargetTemp; + boolean isPrinting; + String error; + } PrinterStruct; + + PrinterStruct printerData; + + +public: + OctoPrintClient(String ApiKey, String server, int port); + void getPrinterJobResults(); + void updateOctoPrintClient(String ApiKey, String server, int port); + + String getAveragePrintTime(); + String getEstimatedPrintTime(); + String getFileName(); + String getFileSize(); + String getLastPrintTime(); + String getProgressCompletion(); + String getProgressFilepos(); + String getProgressPrintTime(); + String getProgressPrintTimeLeft(); + String getState(); + boolean isPrinting(); + boolean isOperational(); + String getTempBedActual(); + String getTempBedTarget(); + String getTempToolActual(); + String getTempToolTarget(); + String getFilamentLength(); + String getError(); +}; + diff --git a/printermonitor/Settings.h b/printermonitor/Settings.h new file mode 100644 index 0000000..c27c60e --- /dev/null +++ b/printermonitor/Settings.h @@ -0,0 +1,76 @@ +/** 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. +*/ + +/****************************************************************************** + * Printer Monitor is designed for the Wemos D1 ESP8266 + * Wemos D1 Mini: https://amzn.to/2qLyKJd + * 0.96" OLED I2C 128x64 Display (12864) SSD1306 + * OLED Display: https://amzn.to/2JDEAUF + ******************************************************************************/ +/****************************************************************************** + * NOTE: The settings here are the default settings for the first loading. + * After loading you will manage changes to the settings via the Web Interface. + * If you want to change settings again in the settings.h, you will need to + * erase the file system on the Wemos or use the “Reset Settings” option in + * the Web Interface. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include "TimeClient.h" +#include "OctoPrintClient.h" +#include "FS.h" +#include "SSD1306Wire.h" +#include "OLEDDisplayUi.h" + +//****************************** +// Start Settings +//****************************** + +// OctoPrint Monitoring -- Monitor your 3D printer OctoPrint Server +String OctoPrintApiKey = ""; // ApiKey from your User Account on OctoPrint +String OctoPrintServer = ""; // IP or Address of your OctoPrint Server (DO NOT include http://) +int OctoPrintPort = 80; // the port you are running your OctoPrint server on (usually 80); + +const int WEBSERVER_PORT = 80; // The port you can access this device on over HTTP +const boolean WEBSERVER_ENABLED = true; // Device will provide a web interface via http://[ip]:[port]/ +char* www_username = "admin"; // User account for the Web Interface +char* www_password = "password"; // Password for the Web Interface +float UtcOffset = -7; // Hour offset from GMT for your timezone +int minutesBetweenDataRefresh = 60; + +// Display Settings +const int I2C_DISPLAY_ADDRESS = 0x3c; // I2C Address of your Display (usually 0x3c or 0x3d) +const int SDA_PIN = D2; +const int SCL_PIN = D5; + +boolean ENABLE_OTA = true; // this will allow you to load firmware to the device over WiFi (see OTA for ESP8266) + +//****************************** +// End Settings +//****************************** + +String themeColor = "light-green"; // this can be changed later in the web interface. diff --git a/printermonitor/TimeClient.cpp b/printermonitor/TimeClient.cpp new file mode 100644 index 0000000..84d7f1d --- /dev/null +++ b/printermonitor/TimeClient.cpp @@ -0,0 +1,152 @@ +/**The MIT License (MIT) + +Copyright (c) 2015 by Daniel Eichhorn + +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. +*/ + +/* +Modified by David Payne for use in the Scrolling Marquee +*/ + +#include "TimeClient.h" + +TimeClient::TimeClient(float utcOffset) { + myUtcOffset = utcOffset; +} + +void TimeClient::updateTime() { + WiFiClient client; + + if (!client.connect(ntpServerName, httpPort)) { + Serial.println("connection failed"); + return; + } + + // This will send the request to the server + client.print(String("GET / HTTP/1.1\r\n") + + String("Host: www.google.com\r\n") + + String("Connection: close\r\n\r\n")); + int repeatCounter = 0; + while(!client.available() && repeatCounter < 10) { + delay(1000); + Serial.println("."); + repeatCounter++; + } + + String line; + + int size = 0; + client.setNoDelay(false); + while(client.connected()) { + while((size = client.available()) > 0) { + line = client.readStringUntil('\n'); + line.toUpperCase(); + // example: + // date: Thu, 19 Nov 2015 20:25:40 GMT + if (line.startsWith("DATE: ")) { + Serial.println(line.substring(23, 25) + ":" + line.substring(26, 28) + ":" +line.substring(29, 31)); + int parsedHours = line.substring(23, 25).toInt(); + int parsedMinutes = line.substring(26, 28).toInt(); + int parsedSeconds = line.substring(29, 31).toInt(); + Serial.println(String(parsedHours) + ":" + String(parsedMinutes) + ":" + String(parsedSeconds)); + + localEpoc = (parsedHours * 60 * 60 + parsedMinutes * 60 + parsedSeconds); + Serial.println(localEpoc); + localMillisAtUpdate = millis(); + client.stop(); + } + } + } + +} + +void TimeClient::setUtcOffset(float utcOffset) { + myUtcOffset = utcOffset; +} + +String TimeClient::getHours() { + if (localEpoc == 0) { + return "--"; + } + int hours = ((getCurrentEpochWithUtcOffset() % 86400L) / 3600) % 24; + if (hours < 10) { + return "0" + String(hours); + } + return String(hours); // print the hour (86400 equals secs per day) + +} +String TimeClient::getMinutes() { + if (localEpoc == 0) { + return "--"; + } + int minutes = ((getCurrentEpochWithUtcOffset() % 3600) / 60); + if (minutes < 10 ) { + // In the first 10 minutes of each hour, we'll want a leading '0' + return "0" + String(minutes); + } + return String(minutes); +} +String TimeClient::getSeconds() { + if (localEpoc == 0) { + return "--"; + } + int seconds = getCurrentEpochWithUtcOffset() % 60; + if ( seconds < 10 ) { + // In the first 10 seconds of each minute, we'll want a leading '0' + return "0" + String(seconds); + } + return String(seconds); +} + +String TimeClient::getAmPmHours() { + int hours = getHours().toInt(); + if (hours >= 13) { + hours = hours - 12; + } + if (hours == 0) { + hours = 12; + } + return String(hours); +} + +String TimeClient::getAmPm() { + int hours = getHours().toInt(); + String ampmValue = "AM"; + if (hours >= 12) { + ampmValue = "PM"; + } + return ampmValue; +} + +String TimeClient::getFormattedTime() { + return getHours() + ":" + getMinutes() + ":" + getSeconds(); +} + +String TimeClient::getAmPmFormattedTime() { + return getAmPmHours() + ":" + getMinutes() + " " + getAmPm(); +} + +long TimeClient::getCurrentEpoch() { + return localEpoc + ((millis() - localMillisAtUpdate) / 1000); +} + +long TimeClient::getCurrentEpochWithUtcOffset() { + return round(getCurrentEpoch() + 3600 * myUtcOffset + 86400L) % 86400L; +} diff --git a/printermonitor/TimeClient.h b/printermonitor/TimeClient.h new file mode 100644 index 0000000..dee7601 --- /dev/null +++ b/printermonitor/TimeClient.h @@ -0,0 +1,62 @@ +/**The MIT License (MIT) + +Copyright (c) 2015 by Daniel Eichhorn + +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. + +See more at http://blog.squix.ch +*/ + +/* +Modified by David Payne for use in the Scrolling Marquee +*/ + +#pragma once + +#include + +#define NTP_PACKET_SIZE 48 + +class TimeClient { + + private: + float myUtcOffset = 0; + long localEpoc = 0; + long localMillisAtUpdate; + const char* ntpServerName = "www.google.com"; + const int httpPort = 80; + byte packetBuffer[ NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets + + public: + TimeClient(float utcOffset); + void updateTime(); + + void setUtcOffset(float utcOffset); + String getHours(); + String getAmPmHours(); + String getAmPm(); + String getMinutes(); + String getSeconds(); + String getFormattedTime(); + String getAmPmFormattedTime(); + long getCurrentEpoch(); + long getCurrentEpochWithUtcOffset(); + +}; + diff --git a/printermonitor/printermonitor.ino b/printermonitor/printermonitor.ino new file mode 100644 index 0000000..87d0d31 --- /dev/null +++ b/printermonitor/printermonitor.ino @@ -0,0 +1,775 @@ + /** 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. +*/ + + /********************************************** + * Edit Settings.h for personalization + ***********************************************/ + +#include "Settings.h" + +#define VERSION "1.0" + +#define HOSTNAME "ESP8266-" +#define CONFIG "/conf.txt" + +/* Useful Constants */ +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24L) + +/* 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_DAY) / SECS_PER_HOUR) +#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) + +// Initialize the oled display for I2C_DISPLAY_ADDRESS +// SDA_PIN and SCL_PIN +SSD1306Wire display(I2C_DISPLAY_ADDRESS, SDA_PIN, SCL_PIN); +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); + +// Set the number of Frames supported +const int numberOfFrames = 3; +FrameCallback frames[numberOfFrames]; + +OverlayCallback overlays[] = { drawHeaderOverlay }; +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; + +// OctoPrint Client +OctoPrintClient printerClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort); +int printerCount = 0; + +//declairing prototypes +void configModeCallback (WiFiManager *myWiFiManager); +int8_t getWifiQuality(); + +ESP8266WebServer server(WEBSERVER_PORT); + +const String WEB_ACTIONS = " Home" + " Configure" + " Reset Settings" + " Forget WiFi" + " About"; + +const String CHANGE_FORM = "

Station Config:

" + "" + "" + "" + "Time Refresh (minutes)

" + "Theme Color

" + "" + "" + "" + "
"; + +const String COLOR_THEMES = "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; + + +// Change the externalLight to the pin you wish to use if other than the Built-in LED +int externalLight = LED_BUILTIN; // LED_BUILTIN is is the built in LED on the Wemos + +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); + + readSettings(); + + // initialize dispaly + display.init(); + display.clear(); + display.display(); + + //display.flipScreenVertically(); + display.setFont(ArialMT_Plain_16); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setContrast(255); // default is 255 + display.drawString(64, 5, "Printer Monitor\nBy Qrome\nV" + 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); + + //or use this for auto generated name ESP + ChipID + wifiManager.autoConnect(); + + //Manual Wifi + String hostname(HOSTNAME); + hostname += String(ESP.getChipId(), HEX); + WiFi.hostname(hostname); + + int cnt = 0; + while (WiFi.status() != WL_CONNECTED) { + digitalWrite(externalLight, LOW); + delay(500); + Serial.print("."); + cnt++; + digitalWrite(externalLight, HIGH); + } + + // 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; + ui.setOverlays(overlays, numberOfOverlays); + + // Inital UI takes care of initalising the display too. + ui.init(); + + // print the received signal strength: + Serial.print("Signal Strength (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("Auth Failed"); + else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); + else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); + else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); + else if (error == OTA_END_ERROR) Serial.println("End Failed"); + }); + ArduinoOTA.begin(); + } + + if (WEBSERVER_ENABLED) { + server.on("/", displayPrinterStatus); + server.on("/systemreset", handleSystemReset); + server.on("/forgetwifi", handleWifiReset); + server.on("/updateconfig", handleUpdateConfig); + server.on("/configure", handleConfigure); + server.onNotFound(redirectHome); + // Start the server + server.begin(); + Serial.println("Server started"); + // Print the IP address + String webAddress = "http://" + WiFi.localIP().toString() + ":" + String(WEBSERVER_PORT) + "/"; + Serial.println("Use this URL : " + webAddress); + display.clear(); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setFont(ArialMT_Plain_10); + display.drawString(64, 10, "Web Interface On"); + display.drawString(64, 20, "You May Connect to IP"); + display.setFont(ArialMT_Plain_16); + display.drawString(64, 30, WiFi.localIP().toString()); + display.drawString(64, 46, "Port: " + String(WEBSERVER_PORT)); + display.display(); + } else { + Serial.println("Web Interface is Disabled"); + display.clear(); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setFont(ArialMT_Plain_10); + display.drawString(64, 10, "Web Interface is Off"); + display.drawString(64, 20, "Enable in Settings.h"); + display.display(); + } + + flashLED(5, 500); +} + +//************************************************************ +// Main Looop +//************************************************************ +void loop() { + + //Get Time Update + if((getMinutesFromLastRefresh() >= minutesBetweenDataRefresh) || lastEpoch == 0) { + getUpdateTime(); + } + + if (lastMinute != timeClient.getMinutes() && !printerClient.isPrinting()) { + // Check status every 60 seconds + digitalWrite(externalLight, LOW); + lastMinute = timeClient.getMinutes(); // reset the check value + printerClient.getPrinterJobResults(); + digitalWrite(externalLight, HIGH); + } else if (printerClient.isPrinting()) { + if (lastSecond != timeClient.getSeconds() && timeClient.getSeconds().endsWith("0")) { + lastSecond = timeClient.getSeconds(); + // every 10 seconds while printing get an update + digitalWrite(externalLight, LOW); + printerClient.getPrinterJobResults(); + digitalWrite(externalLight, HIGH); + } + } + + checkDisplay(); // this will see if we need to turn it on or off for night mode. + + ui.update(); + + if (WEBSERVER_ENABLED) { + server.handleClient(); + } + if (ENABLE_OTA) { + ArduinoOTA.handle(); + } +} + +void getUpdateTime() { + digitalWrite(externalLight, LOW); // turn on the LED + Serial.println(); + + Serial.println("Updating Time..."); + //Update the Time + timeClient.updateTime(); + lastEpoch = timeClient.getCurrentEpoch(); + Serial.println("Local time: " + timeClient.getAmPmFormattedTime()); + + digitalWrite(externalLight, HIGH); // turn off the LED +} + +void handleSystemReset() { + if (!server.authenticate(www_username, www_password)) { + return server.requestAuthentication(); + } + Serial.println("Reset System Configuration"); + if (SPIFFS.remove(CONFIG)) { + redirectHome(); + ESP.restart(); + } +} + +void handleUpdateConfig() { + if (!server.authenticate(www_username, www_password)) { + return server.requestAuthentication(); + } + OctoPrintApiKey = server.arg("octoPrintApiKey"); + OctoPrintServer = server.arg("octoPrintAddress"); + OctoPrintPort = server.arg("octoPrintPort").toInt(); + 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(); + lastEpoch = 0; + redirectHome(); +} + +void handleWifiReset() { + if (!server.authenticate(www_username, www_password)) { + 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 handleConfigure() { + if (!server.authenticate(www_username, www_password)) { + return server.requestAuthentication(); + } + digitalWrite(externalLight, LOW); + 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 = String(CHANGE_FORM); + + form.replace("%OCTOKEY%", OctoPrintApiKey); + form.replace("%OCTOADDRESS%", OctoPrintServer); + form.replace("%OCTOPORT%", String(OctoPrintPort)); + String options = ""; + options.replace(">"+String(minutesBetweenDataRefresh)+"<", " selected>"+String(minutesBetweenDataRefresh)+"<"); + form.replace("%OPTIONS%", options); + String themeOptions = String(COLOR_THEMES); + themeOptions.replace(">"+String(themeColor)+"<", " selected>"+String(themeColor)+"<"); + form.replace("%THEME_OPTIONS%", themeOptions); + form.replace("%UTCOFFSET%", String(UtcOffset)); + form.replace("%USERID%", String(www_username)); + form.replace("%STATIONPASSWORD%", String(www_password)); + + server.sendContent(String(form)); + + html = getFooter(); + server.sendContent(html); + server.sendContent(""); + server.client().stop(); + digitalWrite(externalLight, HIGH); +} + +void displayMessage(String message) { + digitalWrite(externalLight, LOW); + + 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", ""); + String html = getHeader(); + server.sendContent(String(html)); + server.sendContent(String(message)); + html = getFooter(); + server.sendContent(String(html)); + server.sendContent(""); + server.client().stop(); + + digitalWrite(externalLight, HIGH); +} + +void redirectHome() { + // Send them back to the Root Directory + server.sendHeader("Location", String("/"), true); + server.sendHeader("Cache-Control", "no-cache, no-store"); + server.sendHeader("Pragma", "no-cache"); + server.sendHeader("Expires", "-1"); + server.send(302, "text/plain", ""); + server.client().stop(); +} + +String getHeader() { + String menu = String(WEB_ACTIONS); + + String html = ""; + html += "Printer Monitor"; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += ""; + html += "

Printer Monitor

"; + html += ""; + html += "
"; + return html; +} + +String getFooter() { + int8_t rssi = getWifiQuality(); + Serial.print("Signal Strength (RSSI): "); + Serial.print(rssi); + Serial.println("%"); + String html = "


"; + html += "
"; + html += "
"; + if (lastReportStatus != "") { + html += " Report Status: " + lastReportStatus + "
"; + } + html += " Version: " + String(VERSION) + "
"; + html += " Signal Strength: "; + html += String(rssi) + "%"; + html += "
"; + html += ""; + return html; +} + +void displayPrinterStatus() { + digitalWrite(externalLight, LOW); + 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", ""); + server.sendContent(String(getHeader())); + + html += "

Time: " + timeClient.getAmPmHours() + ":" + timeClient.getMinutes() + ":" + timeClient.getSeconds() + " " + timeClient.getAmPm() + "

"; + html += "

"; + if (printerClient.getError() != "") { + html += "Error: " + printerClient.getError() + "
"; + } + html += "Status: " + printerClient.getState() + "
"; + html += "File: " + printerClient.getFileName() + "
"; + float fileSize = printerClient.getFileSize().toFloat(); + if (fileSize > 0) { + fileSize = fileSize / 1024; + html += "File Size: " + String(fileSize) + "KB
"; + } + int filamentLength = printerClient.getFilamentLength().toInt(); + if (filamentLength > 0) { + float fLength = float(filamentLength) / 1000; + html += "Filament: " + String(fLength) + "m
"; + } + + html += "Tool Temperature: " + printerClient.getTempToolActual() + " C
"; + html += "Bed Temperature: " + printerClient.getTempBedActual() + " C
"; + + int val = printerClient.getProgressPrintTimeLeft().toInt(); + int days = elapsedDays(val); + int hours = numberOfHours(val); + int minutes = numberOfMinutes(val); + int seconds = numberOfSeconds(val); + html += "Est. Print Time Left: " + zeroPad(hours) + ":" + zeroPad(minutes) + ":" + zeroPad(seconds) + "
"; + + val = printerClient.getProgressPrintTime().toInt(); + days = elapsedDays(val); + hours = numberOfHours(val); + minutes = numberOfMinutes(val); + seconds = numberOfSeconds(val); + html += "Printing Time: " + zeroPad(hours) + ":" + zeroPad(minutes) + ":" + zeroPad(seconds) + "
"; + html += ""; + html += "

" + printerClient.getProgressCompletion() + "%
"; + html += "

"; + + server.sendContent(String(html)); // spit out what we got + + + server.sendContent(String(getFooter())); + server.sendContent(""); + server.client().stop(); + digitalWrite(externalLight, HIGH); +} + +void configModeCallback (WiFiManager *myWiFiManager) { + Serial.println("Entered config mode"); + Serial.println(WiFi.softAPIP()); + Serial.println("Wifi Manager"); + Serial.println("Please connect to AP"); + Serial.println(myWiFiManager->getConfigPortalSSID()); + Serial.println("To setup Wifi Configuration"); + flashLED(20, 50); +} + +void flashLED(int number, int delayTime) { + for (int inx = 0; inx < number; inx++) { + delay(delayTime); + digitalWrite(externalLight, LOW); + delay(delayTime); + digitalWrite(externalLight, HIGH); + delay(delayTime); + } +} + +void drawScreen1(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(ArialMT_Plain_16); + display->drawString(64 + x, 0 + y, "Bed / Tool Temp"); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(ArialMT_Plain_24); + int bed = printerClient.getTempBedActual().toInt(); + int tool = printerClient.getTempToolActual().toInt(); + display->drawString(2 + x, 14 + y, String(bed)); + display->drawString(64 + x, 14 + y, String(tool)); + display->setFont(ArialMT_Plain_16); + display->drawString(38 + x, 14 + y, "C"); + display->drawString(115 + x, 14 + y, "C"); +} + +void drawScreen2(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(ArialMT_Plain_16); + + display->drawString(64 + x, 0 + y, "Time Remaining"); + //display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(ArialMT_Plain_24); + int val = printerClient.getProgressPrintTimeLeft().toInt(); + int days = elapsedDays(val); + int hours = numberOfHours(val); + int minutes = numberOfMinutes(val); + int seconds = numberOfSeconds(val); + + String time = zeroPad(hours) + ":" + zeroPad(minutes) + ":" + zeroPad(seconds); + display->drawString(64 + x, 14 + y, time); +} + +void drawScreen3(OLEDDisplay *display, OLEDDisplayUiState* state, int16_t x, int16_t y) { + display->setTextAlignment(TEXT_ALIGN_CENTER); + display->setFont(ArialMT_Plain_16); + + display->drawString(64 + x, 0 + y, "Printing Time"); + //display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(ArialMT_Plain_24); + int val = printerClient.getProgressPrintTime().toInt(); + int days = elapsedDays(val); + int hours = numberOfHours(val); + int minutes = numberOfMinutes(val); + int seconds = numberOfSeconds(val); + + String time = zeroPad(hours) + ":" + zeroPad(minutes) + ":" + zeroPad(seconds); + display->drawString(64 + x, 14 + y, time); +} + +String zeroPad(int value) { + String rtnValue = String(value); + if (value < 10) { + rtnValue = "0" + rtnValue; + } + return rtnValue; +} + +void drawHeaderOverlay(OLEDDisplay *display, OLEDDisplayUiState* state) { + display->setColor(WHITE); + display->setFont(ArialMT_Plain_16); + String time = timeClient.getAmPmHours() + ":" + timeClient.getMinutes(); + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->drawString(0, 48, time); + String ampm = timeClient.getAmPm(); + display->setFont(ArialMT_Plain_10); + display->drawString(39, 54, ampm); + display->setFont(ArialMT_Plain_16); + display->setTextAlignment(TEXT_ALIGN_LEFT); + String percent = String(printerClient.getProgressCompletion()) + "%"; + display->drawString(64, 48, percent); + + // Draw indicator to show next update + int updatePos = (printerClient.getProgressCompletion().toFloat() / float(100)) * 128; + display->drawRect(0, 41, 128, 6); + display->drawHorizontalLine(0, 42, updatePos); + display->drawHorizontalLine(0, 43, updatePos); + display->drawHorizontalLine(0, 44, updatePos); + display->drawHorizontalLine(0, 45, updatePos); + + int8_t quality = getWifiQuality(); + for (int8_t i = 0; i < 4; i++) { + for (int8_t j = 0; j < 3 * (i + 2); j++) { + if (quality > i * 25 || j == 0) { + display->setPixel(114 + 4 * i, 63 - j); + } + } + } +} + +// converts the dBm to a range between 0 and 100% +int8_t getWifiQuality() { + int32_t dbm = WiFi.RSSI(); + if(dbm <= -100) { + return 0; + } else if(dbm >= -50) { + return 100; + } else { + return 2 * (dbm + 100); + } +} + + +void writeSettings() { + // Save decoded message to SPIFFS file for playback on power up. + File f = SPIFFS.open(CONFIG, "w"); + if (!f) { + Serial.println("File open failed!"); + } else { + Serial.println("Saving settings now..."); + f.println("UtcOffset=" + String(UtcOffset)); + f.println("octoKey=" + OctoPrintApiKey); + f.println("octoServer=" + OctoPrintServer); + f.println("octoPort=" + String(OctoPrintPort)); + f.println("refreshRate=" + String(minutesBetweenDataRefresh)); + f.println("themeColor=" + themeColor); + f.println("www_username=" + String(www_username)); + f.println("www_password=" + String(www_password)); + } + f.close(); + readSettings(); + timeClient.setUtcOffset(UtcOffset); +} + +void readSettings() { + if (SPIFFS.exists(CONFIG) == false) { + Serial.println("Settings File does not yet exists."); + writeSettings(); + return; + } + File fr = SPIFFS.open(CONFIG, "r"); + String line; + while(fr.available()) { + line = fr.readStringUntil('\n'); + + if (line.indexOf("UtcOffset=") >= 0) { + UtcOffset = line.substring(line.lastIndexOf("UtcOffset=") + 10).toFloat(); + Serial.println("UtcOffset=" + String(UtcOffset)); + } + if (line.indexOf("octoKey=") >= 0) { + OctoPrintApiKey = line.substring(line.lastIndexOf("octoKey=") + 8); + OctoPrintApiKey.trim(); + Serial.println("OctoPrintApiKey=" + OctoPrintApiKey); + } + if (line.indexOf("octoServer=") >= 0) { + OctoPrintServer = line.substring(line.lastIndexOf("octoServer=") + 11); + OctoPrintServer.trim(); + Serial.println("OctoPrintServer=" + OctoPrintServer); + } + if (line.indexOf("octoPort=") >= 0) { + OctoPrintPort = line.substring(line.lastIndexOf("octoPort=") + 9).toInt(); + Serial.println("OctoPrintPort=" + String(OctoPrintPort)); + } + if (line.indexOf("refreshRate=") >= 0) { + minutesBetweenDataRefresh = line.substring(line.lastIndexOf("refreshRate=") + 12).toInt(); + Serial.println("minutesBetweenDataRefresh=" + String(minutesBetweenDataRefresh)); + } + if (line.indexOf("themeColor=") >= 0) { + themeColor = line.substring(line.lastIndexOf("themeColor=") + 11); + themeColor.trim(); + Serial.println("themeColor=" + themeColor); + } + if (line.indexOf("www_username=") >= 0) { + String temp = line.substring(line.lastIndexOf("www_username=") + 13); + temp.trim(); + temp.toCharArray(www_username, sizeof(temp)); + Serial.println("www_username=" + String(www_username)); + } + if (line.indexOf("www_password=") >= 0) { + String temp = line.substring(line.lastIndexOf("www_password=") + 13); + temp.trim(); + temp.toCharArray(www_password, sizeof(temp)); + Serial.println("www_password=" + String(www_password)); + } + } + fr.close(); + timeClient.setUtcOffset(UtcOffset); + printerClient.updateOctoPrintClient(OctoPrintApiKey, OctoPrintServer, OctoPrintPort); + printerClient.getPrinterJobResults(); +} + +int getMinutesFromLastRefresh() { + int minutes = (timeClient.getCurrentEpoch() - lastEpoch) / 60; + return minutes; +} + +int getMinutesFromLastDisplay() { + int minutes = (timeClient.getCurrentEpoch() - displayOffEpoch) / 60; + return minutes; +} + +// Toggle on and off the display if user defined times +void checkDisplay() { + if (displayOn && !(printerClient.isOperational() || printerClient.isPrinting())) { + // Put Display to sleep + display.clear(); + display.display(); + display.setFont(ArialMT_Plain_16); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setContrast(255); // default is 255 + display.drawString(64, 5, "Printer Offline\nSleep Mode..."); + display.display(); + delay(5000); + enableDisplay(false); + return; + } else if (!displayOn) { + if (printerClient.isOperational()) { + // Wake the Screen up + enableDisplay(true); + display.clear(); + display.display(); + display.setFont(ArialMT_Plain_16); + display.setTextAlignment(TEXT_ALIGN_CENTER); + display.setContrast(255); // default is 255 + display.drawString(64, 5, "Printer Online\nWake up..."); + display.display(); + delay(5000); + return; + } + } +} + +void enableDisplay(boolean enable) { + displayOn = enable; + if (enable) { + if (getMinutesFromLastDisplay() >= minutesBetweenDataRefresh) { + // The display has been off longer than the minutes between refresh -- need to get fresh data + lastEpoch = 0; // this should force a data pull + displayOffEpoch = 0; // reset + } + display.displayOn(); + Serial.println("Display was turned ON: " + timeClient.getFormattedTime()); + } else { + display.displayOff(); + Serial.println("Display was turned OFF: " + timeClient.getFormattedTime()); + displayOffEpoch = lastEpoch; + } +}