This commit is contained in:
2025-02-12 21:10:25 +01:00
commit ec0d7d63de
44 changed files with 8699 additions and 0 deletions

471
src/api.cpp Normal file
View File

@ -0,0 +1,471 @@
#include "api.h"
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "commonFS.h"
bool spoolman_connected = false;
String spoolmanUrl = "";
struct SendToApiParams {
String httpType;
String spoolsUrl;
String updatePayload;
};
/*
// Spoolman Data
{
"version":"1.0",
"protocol":"openspool",
"color_hex":"AF7933",
"type":"ABS",
"min_temp":175,
"max_temp":275,
"brand":"Overture"
}
// FilaMan Data
{
"version":"1.0",
"protocol":"openspool",
"color_hex":"AF7933",
"type":"ABS",
"min_temp":175,
"max_temp":275,
"brand":"Overture",
"sm_id":
}
*/
JsonDocument fetchSpoolsForWebsite() {
HTTPClient http;
String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
Serial.print("Rufe Spool-Daten von: ");
Serial.println(spoolsUrl);
http.begin(spoolsUrl);
int httpCode = http.GET();
JsonDocument filteredDoc;
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
Serial.println(error.c_str());
} else {
JsonArray spools = doc.as<JsonArray>();
JsonArray filteredSpools = filteredDoc.to<JsonArray>();
for (JsonObject spool : spools) {
JsonObject filteredSpool = filteredSpools.createNestedObject();
filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"];
JsonObject filament = filteredSpool.createNestedObject("filament");
filament["sm_id"] = spool["id"];
filament["id"] = spool["filament"]["id"];
filament["name"] = spool["filament"]["name"];
filament["material"] = spool["filament"]["material"];
filament["color_hex"] = spool["filament"]["color_hex"];
filament["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"]; // [190,230]
filament["price_meter"] = spool["filament"]["extra"]["price_meter"];
filament["price_gramm"] = spool["filament"]["extra"]["price_gramm"];
JsonObject vendor = filament.createNestedObject("vendor");
vendor["id"] = spool["filament"]["vendor"]["id"];
vendor["name"] = spool["filament"]["vendor"]["name"];
}
}
} else {
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");
Serial.println(httpCode);
}
http.end();
return filteredDoc;
}
JsonDocument fetchAllSpoolsInfo() {
HTTPClient http;
String spoolsUrl = spoolmanUrl + apiUrl + "/spool";
Serial.print("Rufe Spool-Daten von: ");
Serial.println(spoolsUrl);
http.begin(spoolsUrl);
int httpCode = http.GET();
JsonDocument filteredDoc;
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Fehler beim Parsen der JSON-Antwort: ");
Serial.println(error.c_str());
} else {
JsonArray spools = doc.as<JsonArray>();
JsonArray filteredSpools = filteredDoc.to<JsonArray>();
for (JsonObject spool : spools) {
JsonObject filteredSpool = filteredSpools.createNestedObject();
filteredSpool["price"] = spool["price"];
filteredSpool["remaining_weight"] = spool["remaining_weight"];
filteredSpool["used_weight"] = spool["used_weight"];
filteredSpool["extra"]["nfc_id"] = spool["extra"]["nfc_id"];
JsonObject filament = filteredSpool.createNestedObject("filament");
filament["id"] = spool["filament"]["id"];
filament["name"] = spool["filament"]["name"];
filament["material"] = spool["filament"]["material"];
filament["density"] = spool["filament"]["density"];
filament["diameter"] = spool["filament"]["diameter"];
filament["spool_weight"] = spool["filament"]["spool_weight"];
filament["color_hex"] = spool["filament"]["color_hex"];
JsonObject vendor = filament.createNestedObject("vendor");
vendor["id"] = spool["filament"]["vendor"]["id"];
vendor["name"] = spool["filament"]["vendor"]["name"];
JsonObject extra = filament.createNestedObject("extra");
extra["nozzle_temperature"] = spool["filament"]["extra"]["nozzle_temperature"];
extra["price_gramm"] = spool["filament"]["extra"]["price_gramm"];
extra["price_meter"] = spool["filament"]["extra"]["price_meter"];
}
}
} else {
Serial.print("Fehler beim Abrufen der Spool-Daten. HTTP-Code: ");
Serial.println(httpCode);
}
http.end();
return filteredDoc;
}
void sendToApi(void *parameter) {
SendToApiParams* params = (SendToApiParams*)parameter;
// Extrahiere die Werte
String httpType = params->httpType;
String spoolsUrl = params->spoolsUrl;
String updatePayload = params->updatePayload;
HTTPClient http;
http.begin(spoolsUrl);
http.addHeader("Content-Type", "application/json");
int httpCode = http.PUT(updatePayload);
if (httpType == "PATCH") httpCode = http.PATCH(updatePayload);
if (httpCode == HTTP_CODE_OK) {
Serial.println("Gewicht der Spule erfolgreich aktualisiert");
} else {
Serial.println("Fehler beim Aktualisieren des Gewichts der Spule");
oledShowMessage("Spoolman update failed");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
http.end();
// Speicher freigeben
delete params;
vTaskDelete(NULL);
}
uint8_t updateSpoolTagId(String uidString, const char* payload) {
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Fehler beim JSON-Parsing: ");
Serial.println(error.c_str());
return 0;
}
// Überprüfe, ob die erforderlichen Felder vorhanden sind
if (!doc.containsKey("sm_id") || doc["sm_id"] == "") {
Serial.println("Keine Spoolman-ID gefunden.");
return 0;
}
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + doc["sm_id"].as<String>();
Serial.print("Update Spule mit URL: ");
Serial.println(spoolsUrl);
// Update Payload erstellen
JsonDocument updateDoc;
updateDoc["extra"]["nfc_id"] = "\""+uidString+"\"";
String updatePayload;
serializeJson(updateDoc, updatePayload);
Serial.print("Update Payload: ");
Serial.println(updatePayload);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return 0;
}
params->httpType = "PATCH";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
4096, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
return 1;
}
uint8_t updateSpoolWeight(String spoolId, uint16_t weight) {
String spoolsUrl = spoolmanUrl + apiUrl + "/spool/" + spoolId + "/measure";
Serial.print("Update Spule mit URL: ");
Serial.println(spoolsUrl);
// Update Payload erstellen
JsonDocument updateDoc;
updateDoc["weight"] = weight;
String updatePayload;
serializeJson(updateDoc, updatePayload);
Serial.print("Update Payload: ");
Serial.println(updatePayload);
SendToApiParams* params = new SendToApiParams();
if (params == nullptr) {
Serial.println("Fehler: Kann Speicher für Task-Parameter nicht allokieren.");
return 0;
}
params->httpType = "PUT";
params->spoolsUrl = spoolsUrl;
params->updatePayload = updatePayload;
// Erstelle die Task
BaseType_t result = xTaskCreate(
sendToApi, // Task-Funktion
"SendToApiTask", // Task-Name
4096, // Stackgröße in Bytes
(void*)params, // Parameter
0, // Priorität
NULL // Task-Handle (nicht benötigt)
);
return 1;
}
// #### Spoolman init
bool checkSpoolmanExtraFields() {
HTTPClient http;
String checkUrls[] = {
spoolmanUrl + apiUrl + "/field/spool",
spoolmanUrl + apiUrl + "/field/filament"
};
String spoolExtra[] = {
"nfc_id"
};
String filamentExtra[] = {
"nozzle_temperature",
"price_meter",
"price_gramm",
"bambu_setting_id",
"bambu_idx"
};
String spoolExtraFields[] = {
"{\"name\": \"NFC ID\","
"\"key\": \"nfc_id\","
"\"field_type\": \"text\"}"
};
String filamentExtraFields[] = {
"{\"name\": \"Nozzle Temp\","
"\"unit\": \"°C\","
"\"field_type\": \"integer_range\","
"\"default_value\": \"[190,230]\","
"\"key\": \"nozzle_temperature\"}",
"{\"name\": \"Price/m\","
"\"unit\": \"\","
"\"field_type\": \"float\","
"\"key\": \"price_meter\"}",
"{\"name\": \"Price/g\","
"\"unit\": \"\","
"\"field_type\": \"float\","
"\"key\": \"price_gramm\"}",
"{\"name\": \"Bambu Setting ID\","
"\"field_type\": \"text\","
"\"key\": \"bambu_setting_id\"}",
"{\"name\": \"Bambu IDX\","
"\"field_type\": \"text\","
"\"key\": \"bambu_idx\"}"
};
Serial.println("Überprüfe Extrafelder...");
int urlLength = sizeof(checkUrls) / sizeof(checkUrls[0]);
for (uint8_t i = 0; i < urlLength; i++) {
Serial.println();
Serial.println("-------- Prüfe Felder für "+checkUrls[i]+" --------");
http.begin(checkUrls[i]);
int httpCode = http.GET();
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error) {
String* extraFields;
String* extraFieldData;
u16_t extraLength;
if (i == 0) {
extraFields = spoolExtra;
extraFieldData = spoolExtraFields;
extraLength = sizeof(spoolExtra) / sizeof(spoolExtra[0]);
} else {
extraFields = filamentExtra;
extraFieldData = filamentExtraFields;
extraLength = sizeof(filamentExtra) / sizeof(filamentExtra[0]);
}
for (uint8_t s = 0; s < extraLength; s++) {
bool found = false;
for (JsonObject field : doc.as<JsonArray>()) {
if (field.containsKey("key") && field["key"] == extraFields[s]) {
Serial.println("Feld gefunden: " + extraFields[s]);
found = true;
break;
}
}
if (!found) {
Serial.println("Feld nicht gefunden: " + extraFields[s]);
// Extrafeld hinzufügen
http.begin(checkUrls[i] + "/" + extraFields[s]);
http.addHeader("Content-Type", "application/json");
int httpCode = http.POST(extraFieldData[s]);
if (httpCode > 0) {
// Antwortscode und -nachricht abrufen
String response = http.getString();
//Serial.println("HTTP-Code: " + String(httpCode));
//Serial.println("Antwort: " + response);
if (httpCode != HTTP_CODE_OK) {
return false;
}
} else {
// Fehler beim Senden der Anfrage
Serial.println("Fehler beim Senden der Anfrage: " + String(http.errorToString(httpCode)));
return false;
}
http.end();
}
}
}
}
http.end();
}
Serial.println("-------- ENDE Prüfe Felder --------");
Serial.println();
return true;
}
bool checkSpoolmanInstance(const String& url) {
HTTPClient http;
String healthUrl = url + apiUrl + "/health";
Serial.print("Überprüfe Spoolman-Instanz unter: ");
Serial.println(healthUrl);
http.begin(healthUrl);
int httpCode = http.GET();
if (httpCode > 0) {
if (httpCode == HTTP_CODE_OK) {
oledShowMessage("Spoolman available");
vTaskDelay(1000 / portTICK_PERIOD_MS);
String payload = http.getString();
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error && doc.containsKey("status")) {
const char* status = doc["status"];
http.end();
if (!checkSpoolmanExtraFields()) {
Serial.println("Fehler beim Überprüfen der Extrafelder.");
oledShowMessage("Spoolman Error creating Extrafields");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false;
}
spoolman_connected = true;
return strcmp(status, "healthy") == 0;
}
}
}
http.end();
return false;
}
bool saveSpoolmanUrl(const String& url) {
if (!checkSpoolmanInstance(url)) return false;
JsonDocument doc;
doc["url"] = url;
Serial.print("Speichere URL in Datei: ");
Serial.println(url);
if (!saveJsonValue("/spoolman_url.json", doc)) {
Serial.println("Fehler beim Speichern der Spoolman-URL.");
}
spoolmanUrl = url;
return true;
}
String loadSpoolmanUrl() {
JsonDocument doc;
if (loadJsonValue("/spoolman_url.json", doc) && doc.containsKey("url")) {
return doc["url"].as<String>();
}
Serial.println("Keine gültige Spoolman-URL gefunden.");
return "";
}
bool initSpoolman() {
spoolmanUrl = loadSpoolmanUrl();
spoolmanUrl.trim();
if (spoolmanUrl == "") {
Serial.println("Keine Spoolman-URL gefunden.");
return false;
}
bool success = checkSpoolmanInstance(spoolmanUrl);
if (!success) {
Serial.println("Spoolman nicht erreichbar.");
return false;
}
oledShowTopRow();
return true;
}

24
src/api.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef API_H
#define API_H
#include <Arduino.h>
#include <ESPAsyncWebServer.h> // Include for AsyncWebServerRequest
#include "website.h"
#include "display.h"
#include <ArduinoJson.h>
extern bool spoolman_connected;
extern String spoolmanUrl;
bool checkSpoolmanInstance(const String& url);
bool saveSpoolmanUrl(const String& url);
String loadSpoolmanUrl(); // Neue Funktion zum Laden der URL
bool checkSpoolmanExtraFields(); // Neue Funktion zum Überprüfen der Extrafelder
JsonDocument fetchSpoolsForWebsite(); // API-Funktion für die Webseite
JsonDocument fetchAllSpoolsInfo();
void sendAmsData(AsyncWebSocketClient *client); // Neue Funktion zum Senden von AMS-Daten
uint8_t updateSpoolTagId(String uidString, const char* payload); // Neue Funktion zum Aktualisieren eines Spools
uint8_t updateSpoolWeight(String spoolId, uint16_t weight); // Neue Funktion zum Aktualisieren des Gewichts
bool initSpoolman(); // Neue Funktion zum Initialisieren von Spoolman
#endif

486
src/bambu.cpp Normal file
View File

@ -0,0 +1,486 @@
#include "bambu.h"
#include <ArduinoJson.h>
#include <PubSubClient.h>
#include <WiFiManager.h>
#include <SSLClient.h>
#include "bambu_cert.h"
#include "website.h"
#include "nfc.h"
#include "commonFS.h"
#include "esp_task_wdt.h"
#include "config.h"
#include "display.h"
WiFiClient espClient;
SSLClient sslClient(&espClient);
PubSubClient client(sslClient);
TaskHandle_t BambuMqttTask;
String report_topic = "";
//String request_topic = "";
const char* bambu_username = "bblp";
const char* bambu_ip = nullptr;
const char* bambu_accesscode = nullptr;
const char* bambu_serialnr = nullptr;
bool bambu_connected = false;
// Globale Variablen für AMS-Daten
int ams_count = 0;
String amsJsonData; // Speichert das fertige JSON für WebSocket-Clients
AMSData ams_data[MAX_AMS]; // Definition des Arrays
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode) {
JsonDocument doc;
doc["bambu_ip"] = bambu_ip;
doc["bambu_accesscode"] = bambu_accesscode;
doc["bambu_serialnr"] = bambu_serialnr;
if (!saveJsonValue("/bambu_credentials.json", doc)) {
Serial.println("Fehler beim Speichern der Bambu-Credentials.");
return false;
}
vTaskDelay(100 / portTICK_PERIOD_MS);
if (!setupMqtt()) return false;
return true;
}
bool loadBambuCredentials() {
JsonDocument doc;
if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) {
// Temporäre Strings für die Werte
String ip = doc["bambu_ip"].as<String>();
String code = doc["bambu_accesscode"].as<String>();
String serial = doc["bambu_serialnr"].as<String>();
ip.trim();
code.trim();
serial.trim();
// Dynamische Speicherallokation für die globalen Pointer
bambu_ip = strdup(ip.c_str());
bambu_accesscode = strdup(code.c_str());
bambu_serialnr = strdup(serial.c_str());
report_topic = "device/" + String(bambu_serialnr) + "/report";
//request_topic = "device/" + String(bambu_serialnr) + "/request";
return true;
}
Serial.println("Keine gültigen Bambu-Credentials gefunden.");
return false;
}
String findFilamentIdx(String brand, String type) {
// JSON-Dokument für die Filament-Daten erstellen
JsonDocument doc;
// Laden der bambu_filaments.json
if (!loadJsonValue("/bambu_filaments.json", doc)) {
Serial.println("Fehler beim Laden der Filament-Daten");
return "GFL99"; // Fallback auf Generic PLA
}
String searchKey;
// 1. Suche nach Brand + Type Kombination
if (brand == "Bambu" || brand == "Bambulab") {
searchKey = "Bambu " + type;
} else if (brand == "PolyLite") {
searchKey = "PolyLite " + type;
} else if (brand == "eSUN") {
searchKey = "eSUN " + type;
} else if (brand == "Overture") {
searchKey = "Overture " + type;
} else if (brand == "PolyTerra") {
searchKey = "PolyTerra " + type;
}
// Durchsuche alle Einträge nach der Brand + Type Kombination
for (JsonPair kv : doc.as<JsonObject>()) {
if (kv.value().as<String>() == searchKey) {
return kv.key().c_str();
}
}
// 2. Wenn nicht gefunden, suche nach Generic + Type
searchKey = "Generic " + type;
for (JsonPair kv : doc.as<JsonObject>()) {
if (kv.value().as<String>() == searchKey) {
return kv.key().c_str();
}
}
// 3. Wenn immer noch nichts gefunden, gebe GFL99 zurück (Generic PLA)
return "GFL99";
}
bool sendMqttMessage(String payload) {
Serial.println("Sending MQTT message");
Serial.println(payload);
if (client.publish(report_topic.c_str(), payload.c_str()))
{
return true;
}
return false;
}
bool setBambuSpool(String payload) {
/* payload
//// set Spool
{
"print": {
"sequence_id": 0,
"command": "ams_filament_setting",
"ams_id": 0, // AMS ID 0-3 oder externe Spule 255
"tray_id": 0, // Tray ID 0-3 oder externe Spule 254
"tray_color": "000000FF",
"nozzle_temp_min": 170,
"nozzle_temp_max": 200,
"tray_type": "PETG",
"setting_id": "",
"tray_info_idx": "GFG99"
}
}
//// Remove Spool
{
"print":{
"ams_id":255,
"command":"ams_filament_setting",
"nozzle_temp_max": 0,
"nozzle_temp_min": 0,
"sequence_id": 0,
"setting_id": "",
"tray_color": "FFFFFFFF",
"tray_id": 254,
"tray_info_idx": "",
"tray_type": "",
}
}
*/
Serial.println("Setting spool");
// Parse the JSON
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (error) {
Serial.print("Error parsing JSON: ");
Serial.println(error.c_str());
return false;
}
int amsId = doc["amsId"];
int trayId = doc["trayId"];
String color = doc["color"].as<String>();
color.toUpperCase();
int minTemp = doc["nozzle_temp_min"];
int maxTemp = doc["nozzle_temp_max"];
String type = doc["type"].as<String>();
String brand = doc["brand"].as<String>();
String tray_info_idx = findFilamentIdx(brand, type);
doc.clear();
doc["print"]["sequence_id"] = 0;
doc["print"]["command"] = "ams_filament_setting";
doc["print"]["ams_id"] = amsId < 200 ? amsId-1 : 255;
doc["print"]["tray_id"] = trayId < 200 ? trayId-1 : 254;
doc["print"]["tray_color"] = color.length() == 8 ? color : color+"FF";
doc["print"]["nozzle_temp_min"] = minTemp;
doc["print"]["nozzle_temp_max"] = maxTemp;
doc["print"]["tray_type"] = type;
doc["print"]["setting_id"] = "";
doc["print"]["tray_info_idx"] = tray_info_idx;
// Serialize the JSON
String output;
serializeJson(doc, output);
if (sendMqttMessage(output)) {
Serial.println("Spool successfully set");
}
else
{
Serial.println("Failed to set spool");
return false;
}
return true;
}
// init
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String message;
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
// JSON-Dokument parsen
JsonDocument doc;
DeserializationError error = deserializeJson(doc, message);
if (error) {
Serial.print("Fehler beim Parsen des JSON: ");
Serial.println(error.c_str());
return;
}
// Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren
if (doc["print"].containsKey("upgrade_state")) {
// Prüfen ob AMS-Daten vorhanden sind
if (!doc["print"].containsKey("ams") || !doc["print"]["ams"].containsKey("ams")) {
return;
}
JsonArray amsArray = doc["print"]["ams"]["ams"].as<JsonArray>();
// Prüfe ob sich die AMS-Daten geändert haben
bool hasChanges = false;
// Vergleiche jedes AMS und seine Trays
for (int i = 0; i < amsArray.size() && !hasChanges; i++) {
JsonObject amsObj = amsArray[i];
int amsId = amsObj["id"].as<uint8_t>();
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
// Finde das entsprechende AMS in unseren Daten
int storedIndex = -1;
for (int k = 0; k < ams_count; k++) {
if (ams_data[k].ams_id == amsId) {
storedIndex = k;
break;
}
}
if (storedIndex == -1) {
hasChanges = true;
break;
}
// Vergleiche die Trays
for (int j = 0; j < trayArray.size() && j < 4 && !hasChanges; j++) {
JsonObject trayObj = trayArray[j];
if (trayObj["tray_info_idx"].as<String>() != ams_data[storedIndex].trays[j].tray_info_idx ||
trayObj["tray_type"].as<String>() != ams_data[storedIndex].trays[j].tray_type ||
trayObj["tray_color"].as<String>() != ams_data[storedIndex].trays[j].tray_color) {
hasChanges = true;
break;
}
}
}
// Prüfe die externe Spule
if (!hasChanges && doc["print"].containsKey("vt_tray")) {
JsonObject vtTray = doc["print"]["vt_tray"];
bool foundExternal = false;
for (int i = 0; i < ams_count; i++) {
if (ams_data[i].ams_id == 255) {
foundExternal = true;
if (vtTray["tray_info_idx"].as<String>() != ams_data[i].trays[0].tray_info_idx ||
vtTray["tray_type"].as<String>() != ams_data[i].trays[0].tray_type ||
vtTray["tray_color"].as<String>() != ams_data[i].trays[0].tray_color) {
hasChanges = true;
}
break;
}
}
if (!foundExternal) hasChanges = true;
}
if (!hasChanges) return;
// Fortfahren mit der bestehenden Verarbeitung, da Änderungen gefunden wurden
ams_count = amsArray.size();
// Restlicher bestehender Code...
for (int i = 0; i < ams_count && i < 16; i++) {
JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
ams_data[i].ams_id = i; // Setze die AMS-ID
for (int j = 0; j < trayArray.size() && j < 4; j++) { // Annahme: Maximal 4 Trays pro AMS
JsonObject trayObj = trayArray[j];
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
ams_data[i].trays[j].tray_type = trayObj["tray_type"].as<String>();
ams_data[i].trays[j].tray_sub_brands = trayObj["tray_sub_brands"].as<String>();
ams_data[i].trays[j].tray_color = trayObj["tray_color"].as<String>();
ams_data[i].trays[j].nozzle_temp_min = trayObj["nozzle_temp_min"].as<int>();
ams_data[i].trays[j].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>();
}
}
//Serial.println("----------------");
//Serial.println();
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
sendAmsData(nullptr);
// Verarbeite erst die normalen AMS-Daten
for (int i = 0; i < amsArray.size() && i < 16; i++) {
JsonObject amsObj = amsArray[i];
JsonArray trayArray = amsObj["tray"].as<JsonArray>();
ams_data[i].ams_id = amsObj["id"].as<uint8_t>();
for (int j = 0; j < trayArray.size() && j < 4; j++) {
JsonObject trayObj = trayArray[j];
ams_data[i].trays[j].id = trayObj["id"].as<uint8_t>();
ams_data[i].trays[j].tray_info_idx = trayObj["tray_info_idx"].as<String>();
// ... weitere Tray-Daten ...
}
}
// Setze ams_count auf die Anzahl der normalen AMS
ams_count = amsArray.size();
// Wenn externe Spule vorhanden, füge sie hinzu
if (doc["print"].containsKey("vt_tray")) {
JsonObject vtTray = doc["print"]["vt_tray"];
int extIdx = ams_count; // Index für externe Spule
ams_data[extIdx].ams_id = 255; // Spezielle ID für externe Spule
ams_data[extIdx].trays[0].id = 254; // Spezielle ID für externes Tray
ams_data[extIdx].trays[0].tray_info_idx = vtTray["tray_info_idx"].as<String>();
ams_data[extIdx].trays[0].tray_type = vtTray["tray_type"].as<String>();
ams_data[extIdx].trays[0].tray_sub_brands = vtTray["tray_sub_brands"].as<String>();
ams_data[extIdx].trays[0].tray_color = vtTray["tray_color"].as<String>();
ams_data[extIdx].trays[0].nozzle_temp_min = vtTray["nozzle_temp_min"].as<int>();
ams_data[extIdx].trays[0].nozzle_temp_max = vtTray["nozzle_temp_max"].as<int>();
ams_data[extIdx].trays[0].setting_id = vtTray["setting_id"].as<String>();
ams_count++; // Erhöhe ams_count für die externe Spule
}
// Sende die aktualisierten AMS-Daten
sendAmsData(nullptr);
// Erstelle JSON für WebSocket-Clients
JsonDocument wsDoc;
JsonArray wsArray = wsDoc.to<JsonArray>();
for (int i = 0; i < ams_count; i++) {
JsonObject amsObj = wsArray.createNestedObject();
amsObj["ams_id"] = ams_data[i].ams_id;
JsonArray trays = amsObj.createNestedArray("tray");
int maxTrays = (ams_data[i].ams_id == 255) ? 1 : 4;
for (int j = 0; j < maxTrays; j++) {
JsonObject trayObj = trays.createNestedObject();
trayObj["id"] = ams_data[i].trays[j].id;
trayObj["tray_info_idx"] = ams_data[i].trays[j].tray_info_idx;
trayObj["tray_type"] = ams_data[i].trays[j].tray_type;
trayObj["tray_sub_brands"] = ams_data[i].trays[j].tray_sub_brands;
trayObj["tray_color"] = ams_data[i].trays[j].tray_color;
trayObj["nozzle_temp_min"] = ams_data[i].trays[j].nozzle_temp_min;
trayObj["nozzle_temp_max"] = ams_data[i].trays[j].nozzle_temp_max;
trayObj["setting_id"] = ams_data[i].trays[j].setting_id;
}
}
serializeJson(wsArray, amsJsonData);
sendAmsData(nullptr);
}
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// Attempt to connect
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
Serial.println("... re-connected");
// ... and resubscribe
client.subscribe(report_topic.c_str());
bambu_connected = true;
oledShowTopRow();
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
bambu_connected = false;
oledShowTopRow();
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void mqtt_loop(void * parameter) {
oledShowMessage("Bambu Connected");
bambu_connected = true;
oledShowTopRow();
for(;;) {
if (pauseBambuMqttTask) {
vTaskDelay(10000);
}
if (!client.connected()) {
reconnect();
yield();
esp_task_wdt_reset();
vTaskDelay(100);
}
client.loop();
}
}
bool setupMqtt() {
// Wenn Bambu Daten vorhanden
bool success = loadBambuCredentials();
vTaskDelay(100 / portTICK_PERIOD_MS);
if (!success) {
Serial.println("Failed to load Bambu credentials");
oledShowMessage("Bambu Credentials Missing");
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false;
}
if (success && bambu_ip != "" && bambu_accesscode != "" && bambu_serialnr != "") {
sslClient.setCACert(root_ca);
sslClient.setInsecure();
client.setServer(bambu_ip, 8883);
// Verbinden mit dem MQTT-Server
if (client.connect(bambu_serialnr, bambu_username, bambu_accesscode)) {
client.setCallback(mqtt_callback);
client.setBufferSize(5120);
// Optional: Topic abonnieren
client.subscribe(report_topic.c_str());
//client.subscribe(request_topic.c_str());
Serial.println("MQTT-Client initialisiert");
oledShowTopRow();
xTaskCreatePinnedToCore(
mqtt_loop, /* Function to implement the task */
"BambuMqtt", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
mqttTaskPrio, /* Priority of the task */
&BambuMqttTask, /* Task handle. */
mqttTaskCore); /* Core where the task should run */
} else {
Serial.println("Fehler: Konnte sich nicht beim MQTT-Server anmelden");
oledShowMessage("Bambu Connection Failed");
oledShowTopRow();
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false;
}
} else {
Serial.println("Fehler: Keine MQTT-Daten vorhanden");
oledShowMessage("Bambu Credentials Missing");
oledShowTopRow();
vTaskDelay(2000 / portTICK_PERIOD_MS);
return false;
}
return true;
}

37
src/bambu.h Normal file
View File

@ -0,0 +1,37 @@
#ifndef BAMBU_H
#define BAMBU_H
#include <Arduino.h>
#include <ArduinoJson.h>
struct TrayData {
uint8_t id;
String tray_info_idx;
String tray_type;
String tray_sub_brands;
String tray_color;
int nozzle_temp_min;
int nozzle_temp_max;
String setting_id;
};
#define MAX_AMS 17 // 16 normale AMS + 1 externe Spule
extern String amsJsonData; // Für die vorbereiteten JSON-Daten
struct AMSData {
uint8_t ams_id;
TrayData trays[4]; // Annahme: Maximal 4 Trays pro AMS
};
extern bool bambu_connected;
extern int ams_count;
extern AMSData ams_data[MAX_AMS];
bool loadBambuCredentials();
bool saveBambuCredentials(const String& bambu_ip, const String& bambu_serialnr, const String& bambu_accesscode);
bool setupMqtt();
void mqtt_loop(void * parameter);
bool setBambuSpool(String payload);
#endif

45
src/bambu_cert.h Normal file
View File

@ -0,0 +1,45 @@
const char root_ca[] PROGMEM =
"-----BEGIN CERTIFICATE-----\n"
"MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF\n"
"ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6\n"
"b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL\n"
"MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv\n"
"b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj\n"
"ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM\n"
"9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw\n"
"IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6\n"
"VOujw5H5SNz/0egwLX0tdHA234gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L\n"
"93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm\n"
"jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC\n"
"AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA\n"
"A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI\n"
"U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs\n"
"N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv\n"
"o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU\n"
"5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy\n"
"rqXRfboQnoZsG4q5WTP468SQvvG5\n"
"-----END CERTIFICATE-----";
/*
-----BEGIN CERTIFICATE-----
MIIDZTCCAk2gAwIBAgIUV1FckwXElyek1onFnQ9kL7Bk4N8wDQYJKoZIhvcNAQEL
BQAwQjELMAkGA1UEBhMCQ04xIjAgBgNVBAoMGUJCTCBUZWNobm9sb2dpZXMgQ28u
LCBMdGQxDzANBgNVBAMMBkJCTCBDQTAeFw0yMjA0MDQwMzQyMTFaFw0zMjA0MDEw
MzQyMTFaMEIxCzAJBgNVBAYTAkNOMSIwIAYDVQQKDBlCQkwgVGVjaG5vbG9naWVz
IENvLiwgTHRkMQ8wDQYDVQQDDAZCQkwgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDL3pnDdxGOk5Z6vugiT4dpM0ju+3Xatxz09UY7mbj4tkIdby4H
oeEdiYSZjc5LJngJuCHwtEbBJt1BriRdSVrF6M9D2UaBDyamEo0dxwSaVxZiDVWC
eeCPdELpFZdEhSNTaT4O7zgvcnFsfHMa/0vMAkvE7i0qp3mjEzYLfz60axcDoJLk
p7n6xKXI+cJbA4IlToFjpSldPmC+ynOo7YAOsXt7AYKY6Glz0BwUVzSJxU+/+VFy
/QrmYGNwlrQtdREHeRi0SNK32x1+bOndfJP0sojuIrDjKsdCLye5CSZIvqnbowwW
1jRwZgTBR29Zp2nzCoxJYcU9TSQp/4KZuWNVAgMBAAGjUzBRMB0GA1UdDgQWBBSP
NEJo3GdOj8QinsV8SeWr3US+HjAfBgNVHSMEGDAWgBSPNEJo3GdOj8QinsV8SeWr
3US+HjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQABlBIT5ZeG
fgcK1LOh1CN9sTzxMCLbtTPFF1NGGA13mApu6j1h5YELbSKcUqfXzMnVeAb06Htu
3CoCoe+wj7LONTFO++vBm2/if6Jt/DUw1CAEcNyqeh6ES0NX8LJRVSe0qdTxPJuA
BdOoo96iX89rRPoxeed1cpq5hZwbeka3+CJGV76itWp35Up5rmmUqrlyQOr/Wax6
itosIzG0MfhgUzU51A2P/hSnD3NDMXv+wUY/AvqgIL7u7fbDKnku1GzEKIkfH8hm
Rs6d8SCU89xyrwzQ0PR853irHas3WrHVqab3P+qNwR0YirL0Qk7Xt/q3O1griNg2
Blbjg3obpHo9
-----END CERTIFICATE-----
*/

56
src/commonFS.cpp Normal file
View File

@ -0,0 +1,56 @@
#include "commonFS.h"
bool saveJsonValue(const char* filename, const JsonDocument& doc) {
File file = SPIFFS.open(filename, "w");
if (!file) {
Serial.print("Fehler beim Öffnen der Datei zum Schreiben: ");
Serial.println(filename);
return false;
}
return true;
if (serializeJson(doc, file) == 0) {
Serial.println("Fehler beim Serialisieren von JSON.");
file.close();
return false;
}
file.close();
return true;
}
bool loadJsonValue(const char* filename, JsonDocument& doc) {
File file = SPIFFS.open(filename, "r");
if (!file) {
Serial.print("Fehler beim Öffnen der Datei zum Lesen: ");
Serial.println(filename);
return false;
}
DeserializationError error = deserializeJson(doc, file);
file.close();
if (error) {
Serial.print("Fehler beim Deserialisieren von JSON: ");
Serial.println(error.f_str());
return false;
}
return true;
}
bool initializeSPIFFS() {
// Erster Versuch
if (SPIFFS.begin(true)) {
Serial.println("SPIFFS mounted successfully.");
return true;
}
// Formatierung versuchen
Serial.println("Failed to mount SPIFFS. Formatting...");
SPIFFS.format();
// Zweiter Versuch nach Formatierung
if (SPIFFS.begin(true)) {
Serial.println("SPIFFS formatted and mounted successfully.");
return true;
}
Serial.println("SPIFFS initialization failed completely.");
return false;
}

12
src/commonFS.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef COMMONFS_H
#define COMMONFS_H
#include <Arduino.h>
#include <SPIFFS.h>
#include <ArduinoJson.h>
bool saveJsonValue(const char* filename, const JsonDocument& doc);
bool loadJsonValue(const char* filename, JsonDocument& doc);
bool initializeSPIFFS();
#endif

54
src/config.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "config.h"
// ################### Config Bereich Start
// ***** PN532 (RFID)
//#define PN532_SCK 18
//#define PN532_MOSI 23
//#define PN532_SS 5
//#define PN532_MISO 19
const uint8_t PN532_IRQ = 32;
const uint8_t PN532_RESET = 33;
// ***** PN532
// ***** HX711 (Waage)
// HX711 circuit wiring
const uint8_t LOADCELL_DOUT_PIN = 16; //16;
const uint8_t LOADCELL_SCK_PIN = 17; //17;
const uint8_t calVal_eepromAdress = 0;
const uint16_t SCALE_LEVEL_WEIGHT = 500;
uint16_t defaultScaleCalibrationValue = 430;
// ***** HX711
// ***** Display
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// On an ESP32: 21(SDA), 22(SCL)
const int8_t OLED_RESET = -1; // Reset pin # (or -1 if sharing Arduino reset pin)
const uint8_t SCREEN_ADDRESS = 0x3C; ///< See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
const uint8_t SCREEN_WIDTH = 128; // OLED display width, in pixels
const uint8_t SCREEN_HEIGHT = 64; // OLED display height, in pixels
const uint8_t OLED_TOP_START = 0;
const uint8_t OLED_TOP_END = 16;
const uint8_t OLED_DATA_START = 17;
const uint8_t OLED_DATA_END = SCREEN_HEIGHT;
// ***** Display
// ***** Webserver
const uint8_t webserverPort = 80;
// ***** Webserver
// ***** API
const char* apiUrl = "/api/v1";
// ***** API
// ***** Task Prios
uint8_t rfidTaskCore = 1;
uint8_t rfidTaskPrio = 1;
uint8_t rfidWriteTaskPrio = 1;
uint8_t mqttTaskCore = 1;
uint8_t mqttTaskPrio = 1;
uint8_t scaleTaskCore = 0;
uint8_t scaleTaskPrio = 1;
// ***** Task Prios

48
src/config.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef CONFIG_H
#define CONFIG_H
#include <Arduino.h>
extern const uint8_t PN532_IRQ;
extern const uint8_t PN532_RESET;
extern const uint8_t LOADCELL_DOUT_PIN;
extern const uint8_t LOADCELL_SCK_PIN;
extern const uint8_t calVal_eepromAdress;
extern const uint16_t SCALE_LEVEL_WEIGHT;
extern const int8_t OLED_RESET;
extern const uint8_t SCREEN_ADDRESS;
extern const uint8_t SCREEN_WIDTH;
extern const uint8_t SCREEN_HEIGHT;
extern const uint8_t OLED_TOP_START;
extern const uint8_t OLED_TOP_END;
extern const uint8_t OLED_DATA_START;
extern const uint8_t OLED_DATA_END;
extern const char* apiUrl;
extern const uint8_t webserverPort;
extern const unsigned char wifi_on[];
extern const unsigned char wifi_off[];
extern const unsigned char cloud_on[];
extern const unsigned char cloud_off[];
extern const unsigned char icon_failed[];
extern const unsigned char icon_success[];
extern const unsigned char icon_transfer[];
extern const unsigned char icon_loading[];
extern uint8_t rfidTaskCore;
extern uint8_t rfidTaskPrio;
extern uint8_t rfidWriteTaskPrio;
extern uint8_t mqttTaskCore;
extern uint8_t mqttTaskPrio;
extern uint8_t scaleTaskCore;
extern uint8_t scaleTaskPrio;
extern uint16_t defaultScaleCalibrationValue;
#endif

225
src/display.cpp Normal file
View File

@ -0,0 +1,225 @@
#include "display.h"
#include "api.h"
#include <vector>
#include "icons.h"
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
bool wifiOn = false;
void setupDisplay() {
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
Serial.println(F("SSD1306 allocation failed"));
for (;;); // Stoppe das Programm, wenn das Display nicht initialisiert werden kann
}
display.setTextColor(WHITE);
display.clearDisplay();
display.display();
// Show initial display buffer contents on the screen --
// the library initializes this with an Adafruit splash screen.
display.setTextColor(WHITE);
display.display();
delay(1000); // Pause for 2 seconds
oledShowTopRow();
delay(2000);
}
void oledclearline() {
int x, y;
for (y = 0; y < 16; y++) {
for (x = 0; x < SCREEN_WIDTH; x++) {
display.drawPixel(x, y, BLACK);
}
}
display.display();
}
void oledcleardata() {
int x, y;
for (y = OLED_DATA_START; y < OLED_DATA_END; y++) {
for (x = 0; x < SCREEN_WIDTH; x++) {
display.drawPixel(x, y, BLACK);
}
}
display.display();
}
int oled_center_h(String text) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(text, 0, 0, &x1, &y1, &w, &h);
return (SCREEN_WIDTH - w) / 2;
}
int oled_center_v(String text) {
int16_t x1, y1;
uint16_t w, h;
display.getTextBounds(text, 0, OLED_DATA_START, &x1, &y1, &w, &h);
// Zentrierung nur im Datenbereich zwischen OLED_DATA_START und OLED_DATA_END
return OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - h) / 2);
}
std::vector<String> splitTextIntoLines(String text, uint8_t textSize) {
std::vector<String> lines;
display.setTextSize(textSize);
// Text in Wörter aufteilen
std::vector<String> words;
int pos = 0;
while (pos < text.length()) {
// Überspringe Leerzeichen am Anfang
while (pos < text.length() && text[pos] == ' ') pos++;
// Finde nächstes Leerzeichen
int nextSpace = text.indexOf(' ', pos);
if (nextSpace == -1) {
// Letztes Wort
if (pos < text.length()) {
words.push_back(text.substring(pos));
}
break;
}
// Wort hinzufügen
words.push_back(text.substring(pos, nextSpace));
pos = nextSpace + 1;
}
// Wörter zu Zeilen zusammenfügen
String currentLine = "";
for (size_t i = 0; i < words.size(); i++) {
String testLine = currentLine;
if (currentLine.length() > 0) testLine += " ";
testLine += words[i];
// Prüfe ob diese Kombination auf die Zeile passt
int16_t x1, y1;
uint16_t lineWidth, h;
display.getTextBounds(testLine, 0, OLED_DATA_START, &x1, &y1, &lineWidth, &h);
if (lineWidth <= SCREEN_WIDTH) {
// Passt noch in diese Zeile
currentLine = testLine;
} else {
// Neue Zeile beginnen
if (currentLine.length() > 0) {
lines.push_back(currentLine);
currentLine = words[i];
} else {
// Ein einzelnes Wort ist zu lang
lines.push_back(words[i]);
}
}
}
// Letzte Zeile hinzufügen
if (currentLine.length() > 0) {
lines.push_back(currentLine);
}
Serial.println(lines.size());
return lines;
}
void oledShowMultilineMessage(String message, uint8_t size) {
std::vector<String> lines;
int maxLines = 3; // Maximale Anzahl Zeilen für size 2
// Erste Prüfung mit aktueller Größe
lines = splitTextIntoLines(message, size);
// Wenn mehr als maxLines Zeilen, reduziere Textgröße
if (lines.size() > maxLines && size > 1) {
size = 1;
lines = splitTextIntoLines(message, size);
}
// Ausgabe
display.setTextSize(size);
int lineHeight = size * 8;
int totalHeight = lines.size() * lineHeight;
int startY = OLED_DATA_START + ((OLED_DATA_END - OLED_DATA_START - totalHeight) / 2);
for (size_t i = 0; i < lines.size(); i++) {
display.setCursor(oled_center_h(lines[i]), startY + (i * lineHeight));
display.print(lines[i]);
}
display.display();
}
void oledShowMessage(String message, uint8_t size) {
oledcleardata();
display.setTextSize(size);
display.setTextWrap(false);
// Prüfe ob Text in eine Zeile passt
int16_t x1, y1;
uint16_t textWidth, h;
display.getTextBounds(message, 0, 0, &x1, &y1, &textWidth, &h);
// Text passt in eine Zeile?
if (textWidth <= SCREEN_WIDTH) {
display.setCursor(oled_center_h(message), oled_center_v(message));
display.print(message);
display.display();
} else {
oledShowMultilineMessage(message, size);
}
}
void oledShowTopRow() {
oledclearline();
if (bambu_connected == 1) {
display.drawBitmap(50, 0, bitmap_bambu_on , 16, 16, WHITE);
} else {
display.drawBitmap(50, 0, bitmap_off , 16, 16, WHITE);
}
if (spoolman_connected == 1) {
display.drawBitmap(80, 0, bitmap_spoolman_on , 16, 16, WHITE);
} else {
display.drawBitmap(80, 0, bitmap_off , 16, 16, WHITE);
}
if (wifiOn == 1) {
display.drawBitmap(107, 0, wifi_on , 16, 16, WHITE);
} else {
display.drawBitmap(107, 0, wifi_off , 16, 16, WHITE);
}
display.display();
}
void oledShowIcon(const char* icon) {
oledcleardata();
uint16_t iconSize = OLED_DATA_END-OLED_DATA_START;
uint16_t iconStart = (SCREEN_WIDTH - iconSize) / 2;
if (icon == "failed") {
display.drawBitmap(iconStart, OLED_DATA_START, icon_failed , iconSize, iconSize, WHITE);
}
else if (icon == "success") {
display.drawBitmap(iconStart, OLED_DATA_START, icon_success , iconSize, iconSize, WHITE);
}
else if (icon == "transfer") {
display.drawBitmap(iconStart, OLED_DATA_START, icon_transfer , iconSize, iconSize, WHITE);
}
else if (icon == "loading") {
display.drawBitmap(iconStart, OLED_DATA_START, icon_loading , iconSize, iconSize, WHITE);
}
display.display();
}
void oledShowWeight(uint16_t weight) {
// Display Gewicht
oledcleardata();
display.setTextSize(3);
display.setCursor(oled_center_h(String(weight)+" g"), OLED_DATA_START+10);
display.print(weight);
display.print(" g");
display.display();
}

24
src/display.h Normal file
View File

@ -0,0 +1,24 @@
#ifndef DISPLAY_H
#define DISPLAY_H
#include <Arduino.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "config.h"
extern Adafruit_SSD1306 display;
extern bool wifiOn;
void setupDisplay();
void oledclearline();
void oledcleardata();
int oled_center_h(String text);
int oled_center_v(String text);
void oledShowWeight(uint16_t weight);
void oledShowMessage(String message, uint8_t size = 2);
void oledShowTopRow();
void oledShowIcon(const char* icon);
#endif

126
src/icons.h Normal file
View File

@ -0,0 +1,126 @@
#include <Arduino.h>
/*
// Create Icons
https://javl.github.io/image2cpp/
Size: 47x47
BG Color: Black
Invert: True
Ohters in default
*/
const unsigned char wifi_on [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x1f, 0xf8, 0x78, 0x1e, 0x60, 0x06, 0x07, 0xe0,
0x0f, 0xf0, 0x08, 0x10, 0x00, 0x00, 0x03, 0xc0, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
const unsigned char wifi_off [] PROGMEM = {
0x00, 0x00, 0x20, 0x00, 0x30, 0x00, 0x1b, 0xf0, 0x3d, 0xfc, 0x7e, 0x1e, 0x63, 0x06, 0x07, 0xa0,
0x1f, 0xd8, 0x08, 0x60, 0x01, 0xb0, 0x03, 0xd8, 0x01, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'off', 16x16px
const unsigned char bitmap_off [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'spoolman_on', 16x16px
const unsigned char bitmap_spoolman_on [] PROGMEM = {
0x03, 0xc0, 0x08, 0xf0, 0x20, 0xfc, 0x00, 0xfc, 0x40, 0xfe, 0x70, 0xf0, 0xf8, 0xc1, 0xff, 0xc1,
0xff, 0xc1, 0xf9, 0xc1, 0x70, 0xf0, 0x40, 0xfe, 0x00, 0xfc, 0x20, 0xfc, 0x08, 0xf0, 0x03, 0xc0
};
// 'bambu_on', 16x16px
const unsigned char bitmap_bambu_on [] PROGMEM = {
0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x1c, 0x3e, 0x00, 0x3e, 0x40,
0x30, 0x78, 0x00, 0x7c, 0x06, 0x7c, 0x1e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c, 0x3e, 0x7c
};
// 'failed', 47x47px
const unsigned char icon_failed [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff,
0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x1f, 0xfc, 0x7f, 0xf0, 0x00, 0x00, 0x7f,
0x80, 0x03, 0xf8, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00,
0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x07, 0xc0, 0x00, 0x00, 0x0f, 0x80, 0x07, 0x80, 0x00, 0x00,
0x07, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x1f, 0x00, 0x00, 0x00, 0x01, 0xe0, 0x1e, 0x03,
0x80, 0x07, 0x80, 0xf0, 0x3e, 0x03, 0xc0, 0x07, 0x80, 0xf0, 0x3c, 0x03, 0xe0, 0x0f, 0x80, 0x78,
0x3c, 0x01, 0xf0, 0x1f, 0x00, 0x78, 0x78, 0x00, 0xf8, 0x3e, 0x00, 0x38, 0x78, 0x00, 0x7c, 0x7c,
0x00, 0x3c, 0x78, 0x00, 0x3c, 0xf8, 0x00, 0x3c, 0x78, 0x00, 0x3f, 0xf0, 0x00, 0x3c, 0x70, 0x00,
0x1f, 0xf0, 0x00, 0x3c, 0x70, 0x00, 0x0f, 0xe0, 0x00, 0x3c, 0xf0, 0x00, 0x07, 0xc0, 0x00, 0x1c,
0xf0, 0x00, 0x0f, 0xe0, 0x00, 0x3c, 0x70, 0x00, 0x1f, 0xf0, 0x00, 0x3c, 0x78, 0x00, 0x3f, 0xf0,
0x00, 0x3c, 0x78, 0x00, 0x3e, 0xf8, 0x00, 0x3c, 0x78, 0x00, 0x7c, 0x7c, 0x00, 0x3c, 0x78, 0x00,
0xf8, 0x3e, 0x00, 0x3c, 0x3c, 0x01, 0xf0, 0x1f, 0x00, 0x78, 0x3c, 0x03, 0xe0, 0x0f, 0x80, 0x78,
0x3e, 0x03, 0xc0, 0x0f, 0x80, 0xf0, 0x1e, 0x03, 0x80, 0x07, 0x80, 0xf0, 0x1f, 0x00, 0x00, 0x00,
0x01, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x03, 0xe0, 0x07, 0x80, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xc0,
0x00, 0x00, 0x0f, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00,
0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xf8, 0x3f,
0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00,
0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'loading', 47x47px
const unsigned char icon_loading [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80,
0x00, 0x00, 0x00, 0x00, 0x0f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x00, 0x00,
0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00,
0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xfe,
0x00, 0x00, 0x00, 0x3f, 0xf9, 0xfc, 0x00, 0x00, 0x00, 0x7f, 0x83, 0xf8, 0x38, 0x00, 0x00, 0xff,
0x07, 0xf0, 0x7c, 0x00, 0x00, 0xfc, 0x07, 0xe0, 0x7e, 0x00, 0x01, 0xf8, 0x0f, 0xc0, 0x3f, 0x00,
0x03, 0xf0, 0x07, 0x80, 0x1f, 0x00, 0x03, 0xe0, 0x03, 0x00, 0x1f, 0x80, 0x07, 0xe0, 0x00, 0x00,
0x0f, 0x80, 0x07, 0xc0, 0x00, 0x00, 0x0f, 0xc0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x0f, 0x80,
0x00, 0x00, 0x07, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0,
0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00,
0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80, 0x00, 0x00, 0x03, 0xe0, 0x0f, 0x80,
0x00, 0x00, 0x03, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xc0, 0x00, 0x00, 0x07, 0xc0,
0x07, 0xc0, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0x80, 0x03, 0xe0, 0x00, 0x00,
0x1f, 0x80, 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00, 0x01, 0xfc,
0x00, 0x00, 0x7e, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xfc, 0x00, 0x00, 0x7f, 0x80, 0x07, 0xf8, 0x00,
0x00, 0x3f, 0xf0, 0x3f, 0xf0, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff,
0xc0, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00,
0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'success', 47x47px
const unsigned char icon_success [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff,
0x80, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xfc, 0x3f, 0xf0, 0x00, 0x00, 0x7f,
0x80, 0x03, 0xfc, 0x00, 0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00,
0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x03, 0xe0, 0x00, 0x00, 0x07, 0xc0, 0x07, 0xc0, 0x00, 0x00,
0x03, 0xc0, 0x0f, 0x80, 0x00, 0x00, 0x01, 0xe0, 0x0f, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x1e, 0x00,
0x00, 0x00, 0x00, 0xf0, 0x1e, 0x00, 0x00, 0x00, 0xc0, 0xf8, 0x3c, 0x00, 0x00, 0x01, 0xe0, 0x78,
0x3c, 0x00, 0x00, 0x03, 0xe0, 0x78, 0x38, 0x00, 0x00, 0x03, 0xc0, 0x3c, 0x78, 0x00, 0x00, 0x07,
0xc0, 0x3c, 0x78, 0x00, 0x00, 0x0f, 0x80, 0x3c, 0x78, 0x00, 0x00, 0x1f, 0x00, 0x1c, 0x78, 0x00,
0x00, 0x1e, 0x00, 0x1c, 0x78, 0x0f, 0x00, 0x3e, 0x00, 0x1e, 0x78, 0x0f, 0x80, 0x7c, 0x00, 0x1e,
0x78, 0x0f, 0xc0, 0xf8, 0x00, 0x1e, 0x78, 0x07, 0xe0, 0xf0, 0x00, 0x1c, 0x78, 0x03, 0xf1, 0xf0,
0x00, 0x1c, 0x78, 0x01, 0xfb, 0xe0, 0x00, 0x3c, 0x78, 0x00, 0xff, 0xc0, 0x00, 0x3c, 0x38, 0x00,
0x7f, 0x80, 0x00, 0x3c, 0x3c, 0x00, 0x3f, 0x80, 0x00, 0x78, 0x3c, 0x00, 0x1f, 0x00, 0x00, 0x78,
0x1e, 0x00, 0x0e, 0x00, 0x00, 0xf8, 0x1e, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00,
0x01, 0xf0, 0x0f, 0x80, 0x00, 0x00, 0x01, 0xe0, 0x07, 0xc0, 0x00, 0x00, 0x03, 0xc0, 0x03, 0xe0,
0x00, 0x00, 0x07, 0xc0, 0x03, 0xf0, 0x00, 0x00, 0x1f, 0x80, 0x01, 0xf8, 0x00, 0x00, 0x3f, 0x00,
0x00, 0xfe, 0x00, 0x00, 0xfe, 0x00, 0x00, 0x7f, 0x80, 0x03, 0xfc, 0x00, 0x00, 0x1f, 0xfc, 0x3f,
0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00,
0x7f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
// 'transfer', 47x47px
const unsigned char icon_transfer [] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xe0, 0x01,
0xfe, 0x00, 0x00, 0x3f, 0xe0, 0x07, 0xff, 0x80, 0x00, 0xff, 0xc0, 0x0f, 0xff, 0xc0, 0x01, 0xfb,
0xc0, 0x1f, 0x03, 0xe0, 0x03, 0xe3, 0x80, 0x3c, 0x00, 0xf0, 0x07, 0xc3, 0x80, 0x78, 0x00, 0x78,
0x0f, 0x80, 0x00, 0x70, 0x00, 0x78, 0x0f, 0x00, 0x00, 0x70, 0x00, 0x38, 0x1e, 0x00, 0x00, 0xf0,
0x00, 0x3c, 0x1c, 0x00, 0x00, 0xe0, 0x00, 0x3c, 0x3c, 0x00, 0x00, 0xe0, 0x00, 0x1c, 0x38, 0x00,
0x00, 0xe0, 0x00, 0x3c, 0x38, 0x00, 0x00, 0xf0, 0x00, 0x3c, 0x38, 0x00, 0x00, 0x70, 0x00, 0x38,
0x38, 0x00, 0x00, 0x78, 0x00, 0x78, 0x38, 0x00, 0x00, 0x78, 0x00, 0x78, 0x30, 0x00, 0x00, 0x3c,
0x00, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xc0, 0x00, 0x00,
0x00, 0x07, 0xff, 0x80, 0x00, 0x00, 0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xfc, 0x00,
0x00, 0x00, 0x1f, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x1e, 0x00, 0x00, 0x18, 0x38, 0x00,
0x0e, 0x00, 0x00, 0x1c, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x1c, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x3c,
0x38, 0x00, 0x0e, 0x00, 0x00, 0x3c, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x38, 0x38, 0x00, 0x0e, 0x00,
0x00, 0x38, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x78, 0x38, 0x00, 0x0e, 0x00, 0x00, 0x70, 0x38, 0x00,
0x0e, 0x00, 0x00, 0xf0, 0x38, 0x00, 0x0e, 0x00, 0x01, 0xe0, 0x38, 0x00, 0x0e, 0x01, 0x83, 0xe0,
0x38, 0x00, 0x0e, 0x03, 0x87, 0xc0, 0x3c, 0x00, 0x1e, 0x03, 0x9f, 0x80, 0x1f, 0x80, 0xfc, 0x07,
0xff, 0x00, 0x1f, 0xff, 0xf8, 0x07, 0xfc, 0x00, 0x07, 0xff, 0xf0, 0x0f, 0xf0, 0x00, 0x01, 0xff,
0xc0, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

191
src/main.cpp Normal file
View File

@ -0,0 +1,191 @@
#include <Arduino.h>
#include <WiFi.h>
#include <DNSServer.h>
#include <WiFiManager.h>
#include <ESPmDNS.h>
#include <Wire.h>
#include "config.h"
#include "website.h"
#include "api.h"
#include "display.h"
#include "bambu.h"
#include "nfc.h"
#include "scale.h"
#include "esp_task_wdt.h"
#include "commonFS.h"
// ***** WIFI initialisieren
WiFiManager wm;
bool wm_nonblocking = false;
void initWiFi();
// ################### Functions
// ##### SETUP #####
void setup() {
Serial.begin(115200);
// Initialize SPIFFS
initializeSPIFFS();
// Start Display
setupDisplay();
// WiFiManager
initWiFi();
// Webserver
Serial.println("Starte Webserver");
setupWebserver(server);
// Spoolman API
// api.cpp
initSpoolman();
// Bambu MQTT
// bambu.cpp
setupMqtt();
// mDNS
Serial.println("Starte MDNS");
if (!MDNS.begin("filaman")) { // Set the hostname to "esp32.local"
Serial.println("Error setting up MDNS responder!");
while(1) {
delay(1000);
}
}
Serial.println("mDNS responder started");
startNfc();
start_scale();
// WDT initialisieren mit 10 Sekunden Timeout
bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus
esp_task_wdt_init(10, panic);
// Aktuellen Task (loopTask) zum Watchdog hinzufügen
esp_task_wdt_add(NULL);
// Optional: Andere Tasks zum Watchdog hinzufügen, falls nötig
// esp_task_wdt_add(task_handle);
}
unsigned long lastWeightReadTime = 0;
const unsigned long weightReadInterval = 1000; // 1 second
uint8_t weightSend = 0;
int16_t lastWeight = 0;
uint8_t wifiErrorCounter = 0;
// ##### PROGRAM START #####
void loop() {
// Überprüfe den WLAN-Status
if (WiFi.status() != WL_CONNECTED) {
wifiErrorCounter++;
wifiOn = false;
} else {
wifiErrorCounter = 0;
wifiOn = true;
}
if (wifiErrorCounter > 20) ESP.restart();
unsigned long currentMillis = millis();
// Falls WifiManager im nicht blockenden Modus ist
//if(wm_nonblocking) wm.process();
// Ausgabe der Waage auf Display
if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0)
{
(weight < 0) ? oledShowMessage("!! -1") : oledShowWeight(weight);
}
// Wenn Timer abgelaufen und nicht gerade ein RFID-Tag geschrieben wird
if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag < 3)
{
lastWeightReadTime = currentMillis;
// Prüfen ob die Waage korrekt genullt ist
if ((weight > 0 && weight < 5) || weight < 0)
{
scale_tare_counter++;
}
else
{
scale_tare_counter = 0;
}
// Prüfen ob das Gewicht gleich bleibt und dann senden
if (weight == lastWeight && weight > 5)
{
weigthCouterToApi++;
}
else
{
weigthCouterToApi = 0;
weightSend = 0;
}
}
// reset weight counter after writing tag
if (currentMillis - lastWeightReadTime >= weightReadInterval && hasReadRfidTag > 1)
{
weigthCouterToApi = 0;
}
lastWeight = weight;
// Wenn ein Tag mit SM id erkannte wurde und der Waage Counter anspricht an SM Senden
if (spoolId != "" && weigthCouterToApi > 5 && weightSend == 0 && hasReadRfidTag == 1) {
oledShowIcon("loading");
if (updateSpoolWeight(spoolId, weight))
{
oledShowIcon("success");
vTaskDelay(2000 / portTICK_PERIOD_MS);
weightSend = 1;
}
else
{
oledShowIcon("failed");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
yield();
esp_task_wdt_reset();
}
// ##### Funktionen für Konfiguration #####
void initWiFi() {
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
if(wm_nonblocking) wm.setConfigPortalBlocking(false);
wm.setConfigPortalTimeout(320); // Portal nach 5min schließen
oledShowTopRow();
oledShowMessage("WiFi Setup");
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
res = wm.autoConnect("FilaMan"); // anonymous ap
// res = wm.autoConnect("spoolman","password"); // password protected ap
if(!res) {
Serial.println("Failed to connect or hit timeout");
// ESP.restart();
oledShowTopRow();
oledShowMessage("WiFi not connected Check Portal");
}
else {
wifiOn = true;
//if you get here you have connected to the WiFi
Serial.println("connected...yeey :)");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
oledShowTopRow();
display.display();
}
}
// ##### Funktionen für Konfiguration Ende #####

503
src/nfc.cpp Normal file
View File

@ -0,0 +1,503 @@
#include "nfc.h"
#include <Arduino.h>
#include <Adafruit_PN532.h>
#include <ArduinoJson.h>
#include "config.h"
#include "website.h"
#include "api.h"
#include "esp_task_wdt.h"
//Adafruit_PN532 nfc(PN532_SCK, PN532_MISO, PN532_MOSI, PN532_SS);
Adafruit_PN532 nfc(PN532_IRQ, PN532_RESET);
TaskHandle_t RfidReaderTask;
JsonDocument rfidData;
String spoolId = "";
String nfcJsonData = "";
volatile bool pauseBambuMqttTask = false;
volatile uint8_t hasReadRfidTag = 0;
// 0 = nicht gelesen
// 1 = erfolgreich gelesen
// 2 = fehler beim Lesen
// 3 = schreiben
// 4 = fehler beim Schreiben
// 5 = erfolgreich geschrieben
// 6 = reading
// ***** PN532
// ##### Funktionen für RFID #####
void payloadToJson(uint8_t *data) {
const char* startJson = strchr((char*)data, '{');
const char* endJson = strrchr((char*)data, '}');
if (startJson && endJson && endJson > startJson) {
String jsonString = String(startJson, endJson - startJson + 1);
//Serial.print("Bereinigter JSON-String: ");
//Serial.println(jsonString);
// JSON-Dokument verarbeiten
JsonDocument doc; // Passen Sie die Größe an den JSON-Inhalt an
DeserializationError error = deserializeJson(doc, jsonString);
if (!error) {
const char* version = doc["version"];
const char* protocol = doc["protocol"];
const char* color_hex = doc["color_hex"];
const char* type = doc["type"];
int min_temp = doc["min_temp"];
int max_temp = doc["max_temp"];
const char* brand = doc["brand"];
Serial.println();
Serial.println("-----------------");
Serial.println("JSON-Parsed Data:");
Serial.println(version);
Serial.println(protocol);
Serial.println(color_hex);
Serial.println(type);
Serial.println(min_temp);
Serial.println(max_temp);
Serial.println(brand);
Serial.println("-----------------");
Serial.println();
} else {
Serial.print("deserializeJson() failed: ");
Serial.println(error.f_str());
}
} else {
Serial.println("Kein gültiger JSON-Inhalt gefunden oder fehlerhafte Formatierung.");
//writeJsonToTag("{\"version\":\"1.0\",\"protocol\":\"NFC\",\"color_hex\":\"#FFFFFF\",\"type\":\"Example\",\"min_temp\":10,\"max_temp\":30,\"brand\":\"BrandName\"}");
}
}
bool formatNdefTag() {
uint8_t ndefInit[] = { 0x03, 0x00, 0xFE }; // NDEF Initialisierungsnachricht
bool success = true;
int pageOffset = 4; // Startseite für NDEF-Daten auf NTAG2xx
Serial.println();
Serial.println("Formatiere NDEF-Tag...");
// Schreibe die Initialisierungsnachricht auf die ersten Seiten
for (int i = 0; i < sizeof(ndefInit); i += 4) {
if (!nfc.ntag2xx_WritePage(pageOffset + (i / 4), &ndefInit[i])) {
success = false;
break;
}
}
return success;
}
uint8_t ntag2xx_WriteNDEF(const char *payload) {
/*
if (!formatNdefTag()) {
Serial.println("Fehler beim Formatieren des NDEF-Tags.");
hasReadRfidTag = 2;
return 0;
}
*/
uint8_t tagSize = 240; // 144 bytes is maximum for NTAG213
Serial.print("Tag Size: ");Serial.println(tagSize);
uint8_t pageBuffer[4] = {0, 0, 0, 0};
Serial.println("Beginne mit dem Schreiben der NDEF-Nachricht...");
// Figure out how long the string is
uint8_t len = strlen(payload);
Serial.print("Länge der Payload: ");
Serial.println(len);
Serial.print("Payload: ");Serial.println(payload);
// Setup the record header
// See NFCForum-TS-Type-2-Tag_1.1.pdf for details
uint8_t pageHeader[21] = {
/* NDEF Message TLV - JSON Record */
0x03, /* Tag Field (0x03 = NDEF Message) */
(uint8_t)(len+3+16), /* Payload Length (including NDEF header) */
0xD2, /* NDEF Record Header (TNF=0x2:MIME Media + SR + ME + MB) */
0x10, /* Type Length for the record type indicator */
(uint8_t)(len), /* Payload len */
'a', 'p', 'p', 'l', 'i', 'c', 'a', 't', 'i', 'o', 'n', '/', 'j', 's', 'o', 'n'
};
// Make sure the URI payload will fit in dataLen (include 0xFE trailer)
if ((len < 1) || (len + 1 > (tagSize - sizeof(pageHeader))))
{
Serial.println();
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!");
Serial.println("Fehler: Die Nutzlast passt nicht in die Datenlänge.");
Serial.println("!!!!!!!!!!!!!!!!!!!!!!!!");
Serial.println();
return 0;
}
//Serial.println();
//Serial.print("Header Size: ");Serial.println(sizeof(pageHeader));
// Kombiniere Header und Payload
int totalSize = sizeof(pageHeader) + len;
uint8_t* combinedData = (uint8_t*) malloc(totalSize);
if (combinedData == NULL)
{
Serial.println("Fehler: Nicht genug Speicher vorhanden.");
return 0;
}
// Überprüfe die Kombination von Header und Payload
/*
Serial.print("Header: ");
for (int i = 0; i < sizeof(pageHeader); i++) {
Serial.print(pageHeader[i], HEX);
Serial.print(" ");
}
Serial.println();
Serial.print("Payload: ");
for (int i = 0; i < len; i++) {
Serial.print(payload[i], HEX);
Serial.print(" ");
}
Serial.println();
*/
// Kombiniere Header und Payload
memcpy(combinedData, pageHeader, sizeof(pageHeader));
memcpy(&combinedData[sizeof(pageHeader)], payload, len);
// Überprüfe die Kombination von Header und Payload
/*
Serial.print("Kombinierte Daten: ");
for (int i = 0; i < totalSize; i++) {
Serial.print(combinedData[i], HEX);
Serial.print(" ");
}
Serial.println();
*/
// Schreibe die Seiten
uint8_t a = 0;
uint8_t i = 0;
while (totalSize > 0) {
memset(pageBuffer, 0, 4);
int bytesToWrite = (totalSize < 4) ? totalSize : 4;
memcpy(pageBuffer, combinedData + a, bytesToWrite);
// Überprüfe die Schreibung der Seiten
/*
Serial.print("Seite ");
Serial.print(i);
Serial.print(": ");
for (int j = 0; j < bytesToWrite; j++) {
Serial.print(pageBuffer[j], HEX);
Serial.print(" ");
}
Serial.println();
*/
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength;
nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500);
//Serial.print("Schreibe Seite: ");Serial.println(i);
if (!(nfc.ntag2xx_WritePage(4+i, pageBuffer)))
{
Serial.println("Fehler beim Schreiben der Seite.");
free(combinedData);
return 0;
}
//Serial.print("Seite geschrieben: ");Serial.println(i);
yield();
//esp_task_wdt_reset();
i++;
a += 4;
totalSize -= bytesToWrite;
}
// Ensure the NDEF message is properly terminated
memset(pageBuffer, 0, 4);
pageBuffer[0] = 0xFE; // NDEF record footer
if (!(nfc.ntag2xx_WritePage(4+i, pageBuffer)))
{
Serial.println("Fehler beim Schreiben des End-Bits.");
free(combinedData);
return 0;
}
Serial.println("NDEF-Nachricht erfolgreich geschrieben.");
free(combinedData);
return 1;
}
bool decodeNdefAndReturnJson(const byte* encodedMessage) {
byte typeLength = encodedMessage[3];
byte payloadLength = encodedMessage[4];
nfcJsonData = "";
for (int i = 2; i < payloadLength+2; i++)
{
nfcJsonData += (char)encodedMessage[3 + typeLength + i];
}
// JSON-Dokument verarbeiten
JsonDocument doc; // Passen Sie die Größe an den JSON-Inhalt an
DeserializationError error = deserializeJson(doc, nfcJsonData);
if (error)
{
nfcJsonData = "";
Serial.println("Fehler beim Verarbeiten des JSON-Dokuments");
Serial.print("deserializeJson() failed: ");
Serial.println(error.f_str());
return false;
}
else
{
// Sende die aktualisierten AMS-Daten an alle WebSocket-Clients
Serial.println("JSON-Dokument erfolgreich verarbeitet");
Serial.println(doc.as<String>());
if (doc["sm_id"] != "")
{
Serial.println("SPOOL-ID gefunden: " + doc["sm_id"].as<String>());
spoolId = doc["sm_id"].as<String>();
}
else
{
Serial.println("Keine SPOOL-ID gefunden.");
spoolId = "";
oledShowMessage("Unknown Spool");
vTaskDelay(2000 / portTICK_PERIOD_MS);
}
}
return true;
}
void writeJsonToTag(void *parameter) {
const char* payload = (const char*)parameter;
// Gib die erstellte NDEF-Message aus
Serial.println("Erstelle NDEF-Message...");
hasReadRfidTag = 3;
vTaskSuspend(RfidReaderTask);
//pauseBambuMqttTask = true;
// aktualisieren der Website wenn sich der Status ändert
sendNfcData(nullptr);
// Wait 10sec for tag
uint8_t success = 0;
String uidString = "";
for (uint16_t i = 0; i < 20; i++) {
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength;
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 500);
if (success) {
for (uint8_t i = 0; i < uidLength; i++) {
uidString += String(uid[i], HEX);
if (i < uidLength - 1) {
uidString += ":"; // Optional: Trennzeichen hinzufügen
}
}
foundNfcTag(nullptr, success);
break;
}
if (i == 0) oledShowMessage("Waiting for NFC-Tag");
yield();
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1));
}
if (success)
{
oledShowIcon("transfer");
// Schreibe die NDEF-Message auf den Tag
success = ntag2xx_WriteNDEF(payload);
if (success)
{
Serial.println("NDEF-Message erfolgreich auf den Tag geschrieben");
//oledShowMessage("NFC-Tag written");
oledShowIcon("success");
vTaskDelay(2000 / portTICK_PERIOD_MS);
hasReadRfidTag = 5;
// aktualisieren der Website wenn sich der Status ändert
sendNfcData(nullptr);
vTaskResume(RfidReaderTask);
pauseBambuMqttTask = false;
updateSpoolTagId(uidString, payload);
}
else
{
Serial.println("Fehler beim Schreiben der NDEF-Message auf den Tag");
oledShowIcon("failed");
vTaskDelay(2000 / portTICK_PERIOD_MS);
hasReadRfidTag = 4;
}
}
else
{
Serial.println("Fehler: Kein Tag zu schreiben gefunden.");
oledShowMessage("No NFC-Tag found");
vTaskDelay(2000 / portTICK_PERIOD_MS);
hasReadRfidTag = 0;
}
sendWriteResult(nullptr, success);
sendNfcData(nullptr);
vTaskResume(RfidReaderTask);
pauseBambuMqttTask = false;
vTaskDelete(NULL);
}
void startWriteJsonToTag(const char* payload) {
char* payloadCopy = strdup(payload);
// Erstelle die Task
xTaskCreate(
writeJsonToTag, // Task-Funktion
"WriteJsonToTagTask", // Task-Name
4096, // Stackgröße in Bytes
(void*)payloadCopy, // Parameter
rfidWriteTaskPrio, // Priorität
NULL // Task-Handle (nicht benötigt)
);
}
void scanRfidTask(void * parameter) {
Serial.println("RFID Task gestartet");
for(;;) {
// Wenn geschrieben wird Schleife aussetzen
if (hasReadRfidTag != 3)
{
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 }; // Buffer to store the returned UID
uint8_t uidLength;
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength, 1000);
foundNfcTag(nullptr, success);
if (success && hasReadRfidTag != 1)
{
// Display some basic information about the card
Serial.println("Found an ISO14443A card");
hasReadRfidTag = 6;
oledShowIcon("transfer");
vTaskDelay(1000 / portTICK_PERIOD_MS);
if (uidLength == 7)
{
uint8_t data[256];
// We probably have an NTAG2xx card (though it could be Ultralight as well)
Serial.println("Seems to be an NTAG2xx tag (7 byte UID)");
for (uint8_t i = 0; i < 45; i++) {
/*
if (i < uidLength) {
uidString += String(uid[i], HEX);
if (i < uidLength - 1) {
uidString += ":"; // Optional: Trennzeichen hinzufügen
}
}
*/
if (!nfc.mifareclassic_ReadDataBlock(i, data + (i - 4) * 4))
{
break; // Stop if reading fails
}
// Check for NDEF message end
if (data[(i - 4) * 4] == 0xFE)
{
break; // End of NDEF message
}
yield();
esp_task_wdt_reset();
vTaskDelay(pdMS_TO_TICKS(1));
}
if (!decodeNdefAndReturnJson(data))
{
oledShowMessage("NFC-Tag unknown");
vTaskDelay(2000 / portTICK_PERIOD_MS);
hasReadRfidTag = 2;
}
else
{
hasReadRfidTag = 1;
}
}
else
{
Serial.println("This doesn't seem to be an NTAG2xx tag (UUID length != 7 bytes)!");
}
}
if (!success && hasReadRfidTag > 0)
{
hasReadRfidTag = 0;
//uidString = "";
nfcJsonData = "";
Serial.println("Tag entfernt");
oledShowWeight(0);
}
// aktualisieren der Website wenn sich der Status ändert
sendNfcData(nullptr);
}
yield();
}
}
void startNfc() {
nfc.begin(); // Beginne Kommunikation mit RFID Leser
delay(1000);
unsigned long versiondata = nfc.getFirmwareVersion(); // Lese Versionsnummer der Firmware aus
if (! versiondata) { // Wenn keine Antwort kommt
Serial.println("Kann kein RFID Board finden !"); // Sende Text "Kann kein..." an seriellen Monitor
//delay(5000);
//ESP.restart();
oledShowMessage("No RFID Board found");
delay(2000);
}
else {
Serial.print("Chip PN5 gefunden"); Serial.println((versiondata >> 24) & 0xFF, HEX); // Sende Text und Versionsinfos an seriellen
Serial.print("Firmware ver. "); Serial.print((versiondata >> 16) & 0xFF, DEC); // Monitor, wenn Antwort vom Board kommt
Serial.print('.'); Serial.println((versiondata >> 8) & 0xFF, DEC); //
nfc.SAMConfig();
// Set the max number of retry attempts to read from a card
// This prevents us from waiting forever for a card, which is
// the default behaviour of the PN532.
//nfc.setPassiveActivationRetries(0x7F);
//nfc.setPassiveActivationRetries(0xFF);
BaseType_t result = xTaskCreatePinnedToCore(
scanRfidTask, /* Function to implement the task */
"RfidReader", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
rfidTaskPrio, /* Priority of the task */
&RfidReaderTask, /* Task handle. */
rfidTaskCore); /* Core where the task should run */
if (result != pdPASS) {
Serial.println("Fehler beim Erstellen des RFID Tasks");
} else {
Serial.println("RFID Task erfolgreich erstellt");
}
}
}

16
src/nfc.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef NFC_H
#define NFC_H
#include <Arduino.h>
void startNfc();
void scanRfidTask(void * parameter);
void startWriteJsonToTag(const char* payload);
extern TaskHandle_t RfidReaderTask;
extern String nfcJsonData;
extern String spoolId;
extern volatile uint8_t hasReadRfidTag;
extern volatile bool pauseBambuMqttTask;
#endif

214
src/scale.cpp Normal file
View File

@ -0,0 +1,214 @@
#include "nfc.h"
#include <Arduino.h>
#include <ArduinoJson.h>
#include "config.h"
#include "HX711.h"
#include <EEPROM.h>
#include "display.h"
#include "nfc.h"
#include "esp_task_wdt.h"
HX711 scale;
TaskHandle_t ScaleTask;
int16_t weight = 0;
uint8_t weigthCouterToApi = 0;
uint8_t scale_tare_counter = 0;
uint8_t pauseMainTask = 0;
// ##### Funktionen für Waage #####
uint8_t tareScale() {
Serial.println("Tare scale");
scale.tare();
return 1;
}
void scale_loop(void * parameter) {
Serial.println("++++++++++++++++++++++++++++++");
Serial.println("Scale Loop started");
Serial.println("++++++++++++++++++++++++++++++");
for(;;) {
if (scale.is_ready())
{
// Waage nochmal Taren, wenn zu lange Abweichung
if (scale_tare_counter >= 5)
{
scale.tare();
scale_tare_counter = 0;
}
weight = round(scale.get_units());
}
vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten
}
}
void start_scale() {
Serial.println("Prüfe Calibration Value");
long calibrationValue; // calibration value (see example file "Calibration.ino")
//calibrationValue = 696.0; // uncomment this if you want to set the calibration value in the sketch
EEPROM.begin(512);
EEPROM.get(calVal_eepromAdress, calibrationValue); // uncomment this if you want to fetch the calibration value from eeprom
//calibrationValue = EEPROM.read(calVal_eepromAdress);
Serial.print("Read Scale Calibration Value ");
Serial.println(calibrationValue);
scale.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
if (isnan(calibrationValue) || calibrationValue < 1) calibrationValue = defaultScaleCalibrationValue;
oledShowMessage("Scale Tare Please remove all");
for (uint16_t i = 0; i < 2000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
if (scale.wait_ready_timeout(1000))
{
scale.set_scale(calibrationValue); // this value is obtained by calibrating the scale with known weights; see the README for details
scale.tare();
}
// Display Gewicht
oledShowWeight(0);
Serial.println("starte Scale Task");
BaseType_t result = xTaskCreatePinnedToCore(
scale_loop, /* Function to implement the task */
"ScaleLoop", /* Name of the task */
10000, /* Stack size in words */
NULL, /* Task input parameter */
scaleTaskPrio, /* Priority of the task */
&ScaleTask, /* Task handle. */
scaleTaskCore); /* Core where the task should run */
if (result != pdPASS) {
Serial.println("Fehler beim Erstellen des ScaleLoop-Tasks");
} else {
Serial.println("ScaleLoop-Task erfolgreich erstellt");
}
}
uint8_t calibrate_scale() {
long newCalibrationValue;
//vTaskSuspend(RfidReaderTask);
vTaskDelete(RfidReaderTask);
pauseBambuMqttTask = true;
pauseMainTask = 1;
if (scale.wait_ready_timeout(1000))
{
scale.set_scale();
oledShowMessage("Step 1 empty Scale");
for (uint16_t i = 0; i < 5000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
scale.tare();
Serial.println("Tare done...");
Serial.print("Place a known weight on the scale...");
oledShowMessage("Step 2 Place the weight");
for (uint16_t i = 0; i < 5000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
long newCalibrationValue = scale.get_units(10);
Serial.print("Result: ");
Serial.println(newCalibrationValue);
newCalibrationValue = newCalibrationValue/SCALE_LEVEL_WEIGHT;
if (newCalibrationValue > 0)
{
Serial.print("New calibration value has been set to: ");
Serial.println(newCalibrationValue);
Serial.print("Save this value to EEPROM adress ");
Serial.println(calVal_eepromAdress);
//EEPROM.put(calVal_eepromAdress, newCalibrationValue);
EEPROM.put(calVal_eepromAdress, newCalibrationValue);
EEPROM.commit();
EEPROM.get(calVal_eepromAdress, newCalibrationValue);
//newCalibrationValue = EEPROM.read(calVal_eepromAdress);
Serial.print("Read Value ");
Serial.println(newCalibrationValue);
Serial.println("End calibration, revome weight");
oledShowMessage("Remove weight");
for (uint16_t i = 0; i < 2000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
oledShowMessage("Calibration done");
for (uint16_t i = 0; i < 2000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
//ESP.restart();
}
else
{
{
Serial.println("Calibration value is invalid. Please recalibrate.");
oledShowMessage("Calibration ERROR Try again");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
return 0;
}
}
}
else
{
Serial.println("HX711 not found.");
oledShowMessage("HX711 not found");
for (uint16_t i = 0; i < 30000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
return 0;
}
oledShowMessage("Scale Ready");
Serial.println("starte Scale Task");
start_scale();
pauseBambuMqttTask = false;
pauseMainTask = 0;
return 1;
}

18
src/scale.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef SCALE_H
#define SCALE_H
#include <Arduino.h>
#include "HX711.h"
void start_scale();
uint8_t calibrate_scale();
uint8_t tareScale();
extern HX711 scale;
extern int16_t weight;
extern uint8_t weigthCouterToApi;
extern uint8_t scale_tare_counter;
extern uint8_t pauseMainTask;
#endif

334
src/website.cpp Normal file
View File

@ -0,0 +1,334 @@
#include "website.h"
#include "commonFS.h"
#include "api.h"
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
//#include <AsyncWebSocket.h>
#include "bambu.h"
#include "nfc.h"
#include "scale.h"
#include "esp_task_wdt.h"
// Cache-Control Header definieren
#define CACHE_CONTROL "max-age=31536000" // Cache für 1 Jahr
AsyncWebServer server(webserverPort);
AsyncWebSocket ws("/ws");
uint8_t lastSuccess = 0;
uint8_t lastHasReadRfidTag = 0;
void onWsEvent(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
if (type == WS_EVT_CONNECT) {
Serial.println("Neuer Client verbunden!");
// Sende die AMS-Daten an den neuen Client
sendAmsData(client);
sendNfcData(client);
foundNfcTag(client, hasReadRfidTag);
sendWriteResult(client, 0);
} else if (type == WS_EVT_DISCONNECT) {
Serial.println("Client getrennt.");
} else if (type == WS_EVT_DATA) {
String message = String((char*)data);
JsonDocument doc;
deserializeJson(doc, message);
if (doc["type"] == "heartbeat") {
// Sende Heartbeat-Antwort
ws.text(client->id(), "{"
"\"type\":\"heartbeat\","
"\"freeHeap\":" + String(ESP.getFreeHeap()/1024) + ","
"\"bambu_connected\":" + String(bambu_connected) + ","
"\"spoolman_connected\":" + String(spoolman_connected) + ""
"}");
}
else if (doc["type"] == "writeNfcTag") {
if (doc.containsKey("payload")) {
// Versuche NFC-Daten zu schreiben
String payloadString;
serializeJson(doc["payload"], payloadString);
startWriteJsonToTag(payloadString.c_str());
}
}
else if (doc["type"] == "scale") {
uint8_t success = 0;
if (doc["payload"] == "tare") {
success = tareScale();
}
if (doc["payload"] == "calibrate") {
success = calibrate_scale();
}
if (success) {
ws.textAll("{\"type\":\"scale\",\"payload\":\"success\"}");
} else {
ws.textAll("{\"type\":\"scale\",\"payload\":\"error\"}");
}
}
else if (doc["type"] == "setBambuSpool") {
Serial.println(doc["payload"].as<String>());
setBambuSpool(doc["payload"]);
}
else {
Serial.println("Unbekannter WebSocket-Typ: " + doc["type"].as<String>());
}
}
}
// Funktion zum Laden und Ersetzen des Headers in einer HTML-Datei
String loadHtmlWithHeader(const char* filename) {
if (!SPIFFS.exists(filename) || !SPIFFS.exists("/header.html")) {
Serial.println("Fehler: Datei nicht gefunden!");
return "Fehler: Datei nicht gefunden!";
}
// Lade den Header
File headerFile = SPIFFS.open("/header.html", "r");
String header = headerFile.readString();
headerFile.close();
// Lade die Hauptdatei
File file = SPIFFS.open(filename, "r");
String html = file.readString();
file.close();
// Ersetze den Platzhalter mit dem Header
html.replace("{{header}}", header);
return html;
}
void sendWriteResult(AsyncWebSocketClient *client, uint8_t success) {
// Sende Erfolg/Misserfolg an alle Clients
String response = "{\"type\":\"writeNfcTag\",\"success\":" + String(success ? "1" : "0") + "}";
ws.textAll(response);
}
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success) {
if (success == lastSuccess) return;
ws.textAll("{\"type\":\"nfcTag\", \"payload\":{\"found\": " + String(success) + "}}");
sendNfcData(nullptr);
lastSuccess = success;
}
void sendNfcData(AsyncWebSocketClient *client) {
if (lastHasReadRfidTag == hasReadRfidTag) return;
if (hasReadRfidTag == 0) {
ws.textAll("{\"type\":\"nfcData\", \"payload\":{}}");
}
else if (hasReadRfidTag == 1) {
ws.textAll("{\"type\":\"nfcData\", \"payload\":" + nfcJsonData + "}");
}
else if (hasReadRfidTag == 2)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Empty Tag or Data not readable\"}}");
}
else if (hasReadRfidTag == 3)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Schreibe Tag...\"}}");
}
else if (hasReadRfidTag == 4)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Error writing to Tag\"}}");
}
else if (hasReadRfidTag == 5)
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"info\":\"Tag erfolgreich geschrieben\"}}");
}
else
{
ws.textAll("{\"type\":\"nfcData\", \"payload\":{\"error\":\"Something went wrong\"}}");
}
lastHasReadRfidTag = hasReadRfidTag;
}
void sendAmsData(AsyncWebSocketClient *client) {
if (ams_count > 0) {
ws.textAll("{\"type\":\"amsData\", \"payload\":" + amsJsonData + "}");
}
}
void setupWebserver(AsyncWebServer &server) {
// Lade die Spoolman-URL beim Booten
spoolmanUrl = loadSpoolmanUrl();
Serial.print("Geladene Spoolman-URL: ");
Serial.println(spoolmanUrl);
// Route für die Startseite
server.on("/about", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für / erhalten");
String html = loadHtmlWithHeader("/index.html");
request->send(200, "text/html", html);
});
// Route für Waage
server.on("/waage", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /waage erhalten");
String html = loadHtmlWithHeader("/waage.html");
request->send(200, "text/html", html);
});
// Route für RFID
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid erhalten");
String html = loadHtmlWithHeader("/rfid.html");
request->send(200, "text/html", html);
Serial.println("RFID-Seite gesendet");
});
/*
// Neue API-Route für das Abrufen der Spool-Daten
server.on("/api/spools", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("API-Aufruf: /api/spools");
JsonDocument spoolsData = fetchSpoolsForWebsite();
String response;
serializeJson(spoolsData, response);
request->send(200, "application/json", response);
});
*/
server.on("/api/url", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("API-Aufruf: /api/url");
String jsonResponse = "{\"spoolman_url\": \"" + String(spoolmanUrl) + "\"}";
request->send(200, "application/json", jsonResponse);
});
// Route für WiFi
server.on("/wifi", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /wifi erhalten");
String html = loadHtmlWithHeader("/wifi.html");
request->send(200, "text/html", html);
});
// Route für Spoolman Setting
server.on("/spoolman", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /spoolman erhalten");
String html = loadHtmlWithHeader("/spoolman.html");
html.replace("{{spoolmanUrl}}", spoolmanUrl);
JsonDocument doc;
if (loadJsonValue("/bambu_credentials.json", doc) && doc.containsKey("bambu_ip")) {
html.replace("{{bambuIp}}", doc["bambu_ip"].as<String>() ? doc["bambu_ip"].as<String>() : "");
html.replace("{{bambuSerial}}", doc["bambu_serialnr"].as<String>() ? doc["bambu_serialnr"].as<String>() : "");
html.replace("{{bambuCode}}", doc["bambu_accesscode"].as<String>() ? doc["bambu_accesscode"].as<String>() : "");
}
request->send(200, "text/html", html);
});
// Route für das Überprüfen der Spoolman-Instanz
server.on("/api/checkSpoolman", HTTP_GET, [](AsyncWebServerRequest *request){
if (!request->hasParam("url")) {
request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing URL parameter\"}");
return;
}
String url = request->getParam("url")->value();
url.trim();
bool healthy = saveSpoolmanUrl(url);
String jsonResponse = "{\"healthy\": " + String(healthy ? "true" : "false") + "}";
request->send(200, "application/json", jsonResponse);
});
// Route für das Überprüfen der Spoolman-Instanz
server.on("/api/bambu", HTTP_GET, [](AsyncWebServerRequest *request){
if (!request->hasParam("bambu_ip") || !request->hasParam("bambu_serialnr") || !request->hasParam("bambu_accesscode")) {
request->send(400, "application/json", "{\"success\": false, \"error\": \"Missing parameter\"}");
return;
}
String bambu_ip = request->getParam("bambu_ip")->value();
String bambu_serialnr = request->getParam("bambu_serialnr")->value();
String bambu_accesscode = request->getParam("bambu_accesscode")->value();
bambu_ip.trim();
bambu_serialnr.trim();
bambu_accesscode.trim();
bool success = saveBambuCredentials(bambu_ip, bambu_serialnr, bambu_accesscode);
request->send(200, "application/json", "{\"healthy\": " + String(success ? "true" : "false") + "}");
});
// Route für das Überprüfen der Spoolman-Instanz
server.on("/reboot", HTTP_GET, [](AsyncWebServerRequest *request){
ESP.restart();
});
// Route für das Laden der CSS-Datei
server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Lade style.css");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/style.css.gz", "text/css");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("style.css gesendet");
});
// Route für das Logo
server.on("/logo.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/logo.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("logo.png gesendet");
});
// Route für Favicon
server.on("/favicon.ico", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/favicon.ico", "image/x-icon");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("favicon.ico gesendet");
});
// Route für spool_in.png
server.on("/spool_in.png", HTTP_GET, [](AsyncWebServerRequest *request){
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spool_in.png.gz", "image/png");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("spool_in.png gesendet");
});
// Route für JavaScript Dateien
server.on("/spoolman.js", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /spoolman.js erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS, "/spoolman.js.gz", "text/javascript");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("Spoolman.js gesendet");
});
server.on("/rfid.js", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("Anfrage für /rfid.js erhalten");
AsyncWebServerResponse *response = request->beginResponse(SPIFFS,"/rfid.js.gz", "text/javascript");
response->addHeader("Content-Encoding", "gzip");
response->addHeader("Cache-Control", CACHE_CONTROL);
request->send(response);
Serial.println("RFID.js gesendet");
});
// Fehlerbehandlung für nicht gefundene Seiten
server.onNotFound([](AsyncWebServerRequest *request){
Serial.print("404 - Nicht gefunden: ");
Serial.println(request->url());
request->send(404, "text/plain", "Seite nicht gefunden");
});
// WebSocket-Route
ws.onEvent(onWsEvent);
server.addHandler(&ws);
ws.enable(true);
// Starte den Webserver
server.begin();
Serial.println("Webserver gestartet");
}

26
src/website.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef WEBSITE_H
#define WEBSITE_H
#include <Arduino.h>
#include <ESPAsyncWebServer.h>
#include "commonFS.h"
#include "api.h"
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <AsyncWebSocket.h>
#include "bambu.h"
#include "nfc.h"
#include "scale.h"
#include "esp_task_wdt.h"
extern String spoolmanUrl;
extern AsyncWebServer server;
extern AsyncWebSocket ws;
void setupWebserver(AsyncWebServer &server);
void sendAmsData(AsyncWebSocketClient *client);
void sendNfcData(AsyncWebSocketClient *client);
void foundNfcTag(AsyncWebSocketClient *client, uint8_t success);
void sendWriteResult(AsyncWebSocketClient *client, uint8_t success);
#endif