Compare commits

...

12 Commits

Author SHA1 Message Date
83f2f0834d Merge pull request #15 from ManuelW77/main
set to main state
2025-03-03 17:10:35 +01:00
6632aa8f95 Merge pull request #14 from janecker/scale-calibration-rework
Reworks the scale calibration handling
2025-03-03 17:08:06 +01:00
8a558c3121 refactor: remove unnecessary delay in MQTT setup and add delay before restart 2025-03-03 16:58:24 +01:00
Jan Philipp Ecker
d434fde92e Reworks the scale calibration handling
Fixes some issues in the scale handling. Prevents a wdg reset after
after scale calibration. Also makes sure that after calibration all
tasks are started again that have been suspsended before.
2025-03-03 16:50:46 +01:00
5afb60df32 fix: correct typo in console log for total length 2025-03-02 20:21:27 +01:00
3394e6eb01 feat: add new 3D print file for Filaman scale 2025-03-02 08:06:59 +01:00
3818c2c059 refactor: remove redundant scale calibration checks and enhance task management 2025-03-01 18:50:20 +01:00
0afc543b5f refactor: enhance AMS data handling and streamline spool auto-setting logic 2025-03-01 18:44:35 +01:00
adee46e3fc refactor: adjust stack size and improve scale calibration logic 2025-03-01 18:44:29 +01:00
1db74867e6 refactor: update labels and input types for better clarity and functionality 2025-03-01 18:44:17 +01:00
0f24a63d32 added Discord Server 2025-03-01 15:33:39 +01:00
3640809502 update documentation for clarity and accuracy 2025-03-01 13:04:28 +01:00
12 changed files with 15399 additions and 79 deletions

View File

@@ -8,7 +8,8 @@ Das System integriert sich nahtlos mit der [Spoolman](https://github.com/Donkie/
Weitere Bilder finden Sie im [img Ordner](/img/) Weitere Bilder finden Sie im [img Ordner](/img/)
oder auf meiner Website: [FilaMan Website](https://www.filaman.app) oder auf meiner Website: [FilaMan Website](https://www.filaman.app)
Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU) Deutsches Erklärvideo: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Es gibt jetzt auch ein Wiki, dort sind nochmal alle Funktionen beschrieben: [Wiki](https://github.com/ManuelW77/Filaman/wiki) ### Es gibt jetzt auch ein Wiki, dort sind nochmal alle Funktionen beschrieben: [Wiki](https://github.com/ManuelW77/Filaman/wiki)

View File

@@ -6,12 +6,14 @@ FilaMan is a filament management system for 3D printing. It uses ESP32 hardware
Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface. Users can manage filament spools, monitor the status of the Automatic Material System (AMS) and make settings via a web interface.
The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format. The system integrates seamlessly with [Bambulab](https://bambulab.com/en-us) 3D printers and [Spoolman](https://github.com/Donkie/Spoolman) filament management as well as the [Openspool](https://github.com/spuder/OpenSpool) NFC-TAG format.
![Scale](./img/scale_trans.png) ![Scale](./img/scale_trans.png)
More Images can be found in the [img Folder](/img/) More Images can be found in the [img Folder](/img/)
or my website:[FilaMan Website](https://www.filaman.app) or my website: [FilaMan Website](https://www.filaman.app)
german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU) german explanatory video: [Youtube](https://youtu.be/uNDe2wh9SS8?si=b-jYx4I1w62zaOHU)
Discord Server: [https://discord.gg/vMAx2gf5](https://discord.gg/vMAx2gf5)
### Now more detailed informations about the usage: [Wiki](https://github.com/ManuelW77/Filaman/wiki) ### Now more detailed informations about the usage: [Wiki](https://github.com/ManuelW77/Filaman/wiki)

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -154,11 +154,11 @@
<p>If activated, FilaMan will automatically update the next filled tray with the last scanned and weighed spool.</p> <p>If activated, FilaMan will automatically update the next filled tray with the last scanned and weighed spool.</p>
<div class="input-group" style="display: flex; margin-bottom: 0;"> <div class="input-group" style="display: flex; margin-bottom: 0;">
<label for="autoSend" style="width: 250px; margin-right: 5px;">Auto Send to Bambu:</label> <label for="autoSend" style="width: 250px; margin-right: 5px;">Auto Send to Bambu:</label>
<label for="autoSendTime" style="width: 250px; margin-right: 5px;">Wait time in Seconds:</label> <label for="autoSendTime" style="width: 250px; margin-right: 5px;">Wait for Spool in Sec:</label>
</div> </div>
<div class="input-group" style="display: flex;"> <div class="input-group" style="display: flex;">
<input type="checkbox" id="autoSend" {{autoSendToBambu}} style="width: 190px; margin-right: 10px;"> <input type="checkbox" id="autoSend" {{autoSendToBambu}} style="width: 190px; margin-right: 10px;">
<input type="text" id="autoSendTime" placeholder="Time to wait for new Spool" value="{{autoSendTime}}" style="width: 100px;"> <input type="number" min="60" id="autoSendTime" placeholder="Time to wait" value="{{autoSendTime}}" style="width: 100px;">
</div> </div>
<button style="margin: 0;" onclick="saveBambuCredentials()">Save Bambu Credentials</button> <button style="margin: 0;" onclick="saveBambuCredentials()">Save Bambu Credentials</button>

View File

@@ -86,7 +86,7 @@ function populateVendorDropdown(data, selectedSmId = null) {
}); });
// Nach der Schleife: Formatierung der Gesamtlänge // Nach der Schleife: Formatierung der Gesamtlänge
console.log("Total Lenght: ", totalLength); console.log("Total Length: ", totalLength);
const formattedLength = totalLength > 1000 const formattedLength = totalLength > 1000
? (totalLength / 1000).toFixed(2) + " km" ? (totalLength / 1000).toFixed(2) + " km"
: totalLength.toFixed(2) + " m"; : totalLength.toFixed(2) + " m";

View File

@@ -188,14 +188,18 @@ label {
font-weight: bold; font-weight: bold;
} }
input[type="text"], input[type="submit"] { input[type="text"], input[type="submit"], input[type="number"] {
padding: 10px; padding: 10px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5px; border-radius: 5px;
font-size: 16px; font-size: 16px;
} }
input[type="text"]:focus { input[type="number"] {
width: 108px !important;
}
input[type="text"]:focus, input[type="number"]:focus {
border-color: #007bff; border-color: #007bff;
outline: none; outline: none;
} }

BIN
img/7-enable.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -341,7 +341,7 @@ void updateAmsWsData(JsonDocument& doc, JsonArray& amsArray, int& ams_count, Jso
ams_data[i].trays[j].tray_color = trayObj["tray_color"].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_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].nozzle_temp_max = trayObj["nozzle_temp_max"].as<int>();
//ams_data[i].trays[j].setting_id = trayObj["setting_id"].as<String>(); if (trayObj["tray_type"].as<String>() == "") ams_data[i].trays[j].setting_id = "";
ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>(); ams_data[i].trays[j].cali_idx = trayObj["cali_idx"].as<String>();
} }
} }
@@ -425,16 +425,8 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
return; return;
} }
// Wenn bambu auto set spool aktiv und eine spule erkannt und mqtt meldung das neue spule im ams
if (autoSendToBambu && autoSetToBambuSpoolId > 0 &&
doc["print"]["command"].as<String>() == "push_status" && doc["print"]["ams"]["tray_pre"].as<uint8_t>()
&& !doc["print"]["ams"]["ams"].as<JsonArray>())
{
autoSetSpool(autoSetToBambuSpoolId, doc["print"]["ams"]["tray_pre"].as<uint8_t>());
}
// Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren // Prüfen, ob "print->upgrade_state" und "print.ams.ams" existieren
if (doc["print"]["upgrade_state"].is<JsonObject>()) if (doc["print"]["upgrade_state"].is<JsonObject>() || (doc["print"]["command"].is<String>() && doc["print"]["command"] == "push_status"))
{ {
// Prüfen ob AMS-Daten vorhanden sind // Prüfen ob AMS-Daten vorhanden sind
if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>()) if (!doc["print"]["ams"].is<JsonObject>() || !doc["print"]["ams"]["ams"].is<JsonArray>())
@@ -443,7 +435,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
} }
JsonArray amsArray = doc["print"]["ams"]["ams"].as<JsonArray>(); JsonArray amsArray = doc["print"]["ams"]["ams"].as<JsonArray>();
// Prüfe ob sich die AMS-Daten geändert haben // Prüfe ob sich die AMS-Daten geändert haben
bool hasChanges = false; bool hasChanges = false;
@@ -479,6 +471,12 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
(trayObj["setting_id"].as<String>() != "" && trayObj["setting_id"].as<String>() != ams_data[storedIndex].trays[j].setting_id) || (trayObj["setting_id"].as<String>() != "" && trayObj["setting_id"].as<String>() != ams_data[storedIndex].trays[j].setting_id) ||
trayObj["cali_idx"].as<String>() != ams_data[storedIndex].trays[j].cali_idx) { trayObj["cali_idx"].as<String>() != ams_data[storedIndex].trays[j].cali_idx) {
hasChanges = true; hasChanges = true;
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
{
autoSetSpool(autoSetToBambuSpoolId, ams_data[storedIndex].trays[j].id);
}
break; break;
} }
} }
@@ -497,6 +495,11 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) {
(vtTray["setting_id"].as<String>() != "" && vtTray["setting_id"].as<String>() != ams_data[i].trays[0].setting_id) || (vtTray["setting_id"].as<String>() != "" && vtTray["setting_id"].as<String>() != ams_data[i].trays[0].setting_id) ||
(vtTray["tray_type"].as<String>() != "" && vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx)) { (vtTray["tray_type"].as<String>() != "" && vtTray["cali_idx"].as<String>() != ams_data[i].trays[0].cali_idx)) {
hasChanges = true; hasChanges = true;
if (autoSendToBambu && autoSetToBambuSpoolId > 0 && hasChanges)
{
autoSetSpool(autoSetToBambuSpoolId, 254);
}
} }
break; break;
} }
@@ -600,7 +603,6 @@ void mqtt_loop(void * parameter) {
bool setupMqtt() { bool setupMqtt() {
// Wenn Bambu Daten vorhanden // Wenn Bambu Daten vorhanden
bool success = loadBambuCredentials(); bool success = loadBambuCredentials();
vTaskDelay(100 / portTICK_PERIOD_MS);
if (!success) { if (!success) {
Serial.println("Failed to load Bambu credentials"); Serial.println("Failed to load Bambu credentials");
@@ -664,6 +666,7 @@ bool setupMqtt() {
void bambu_restart() { void bambu_restart() {
if (BambuMqttTask) { if (BambuMqttTask) {
vTaskDelete(BambuMqttTask); vTaskDelete(BambuMqttTask);
delay(10);
} }
setupMqtt(); setupMqtt();
} }

View File

@@ -13,6 +13,9 @@
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "commonFS.h" #include "commonFS.h"
bool mainTaskWasPaused = 0;
uint8_t scaleTareCounter = 0;
// ##### SETUP ##### // ##### SETUP #####
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
@@ -45,32 +48,14 @@ void setup() {
// NFC Reader // NFC Reader
startNfc(); startNfc();
uint8_t scaleCalibrated = start_scale(); start_scale();
if (scaleCalibrated == 3) {
oledShowMessage("Scale not calibrated!");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
} else if (scaleCalibrated == 0) {
oledShowMessage("HX711 not found");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
}
// WDT initialisieren mit 10 Sekunden Timeout // WDT initialisieren mit 10 Sekunden Timeout
bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus bool panic = true; // Wenn true, löst ein WDT-Timeout einen System-Panik aus
esp_task_wdt_init(10, panic); esp_task_wdt_init(10, panic);
// Aktuellen Task (loopTask) zum Watchdog hinzufügen // Aktuellen Task (loopTask) zum Watchdog hinzufügen
esp_task_wdt_add(NULL); esp_task_wdt_add(NULL);
// Optional: Andere Tasks zum Watchdog hinzufügen, falls nötig
// esp_task_wdt_add(task_handle);
} }
@@ -147,9 +132,17 @@ void loop() {
} }
// Ausgabe der Waage auf Display // Ausgabe der Waage auf Display
if (pauseMainTask == 0 && weight != lastWeight && hasReadRfidTag == 0 && (!autoSendToBambu || autoSetToBambuSpoolId == 0)) if(pauseMainTask == 0)
{ {
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight); if (mainTaskWasPaused || (weight != lastWeight && hasReadRfidTag == 0 && (!autoSendToBambu || autoSetToBambuSpoolId == 0)))
{
(weight < 2) ? ((weight < -2) ? oledShowMessage("!! -0") : oledShowWeight(0)) : oledShowWeight(weight);
}
mainTaskWasPaused = false;
}
else
{
mainTaskWasPaused = true;
} }
@@ -161,11 +154,20 @@ void loop() {
// Prüfen ob die Waage korrekt genullt ist // Prüfen ob die Waage korrekt genullt ist
if ((weight > 0 && weight < 5) || weight < 0) if ((weight > 0 && weight < 5) || weight < 0)
{ {
scale_tare_counter++; if(scaleTareCounter < 5)
{
scaleTareCounter++;
}
else
{
scaleTareRequest = true;
scaleTareCounter = 0;
}
} }
else else
{ {
scale_tare_counter = 0; scaleTareCounter = 0;
} }
// Prüfen ob das Gewicht gleich bleibt und dann senden // Prüfen ob das Gewicht gleich bleibt und dann senden
@@ -209,7 +211,6 @@ void loop() {
vTaskDelay(2000 / portTICK_PERIOD_MS); vTaskDelay(2000 / portTICK_PERIOD_MS);
} }
} }
yield();
esp_task_wdt_reset(); esp_task_wdt_reset();
} }

View File

@@ -14,7 +14,7 @@ TaskHandle_t ScaleTask;
int16_t weight = 0; int16_t weight = 0;
uint8_t weigthCouterToApi = 0; uint8_t weigthCouterToApi = 0;
uint8_t scale_tare_counter = 0; bool scaleTareRequest = false;
uint8_t pauseMainTask = 0; uint8_t pauseMainTask = 0;
uint8_t scaleCalibrated = 1; uint8_t scaleCalibrated = 1;
@@ -34,30 +34,32 @@ void scale_loop(void * parameter) {
Serial.println("++++++++++++++++++++++++++++++"); Serial.println("++++++++++++++++++++++++++++++");
Serial.println("Scale Loop started"); Serial.println("Scale Loop started");
Serial.println("++++++++++++++++++++++++++++++"); Serial.println("++++++++++++++++++++++++++++++");
for(;;) { for(;;) {
if (scale.is_ready()) if (scale.is_ready())
{ {
// Waage nochmal Taren, wenn zu lange Abweichung // Waage nochmal Taren, wenn zu lange Abweichung
if (scale_tare_counter >= 5) if (scaleTareRequest == true)
{ {
Serial.println("Re-Tare scale");
scale.tare(); scale.tare();
scale_tare_counter = 0; scaleTareRequest = false;
} }
weight = round(scale.get_units()); weight = round(scale.get_units());
} }
vTaskDelay(pdMS_TO_TICKS(100)); // Verzögerung, um die CPU nicht zu überlasten vTaskDelay(pdMS_TO_TICKS(100));
} }
} }
uint8_t start_scale() { void start_scale() {
Serial.println("Prüfe Calibration Value"); Serial.println("Prüfe Calibration Value");
long calibrationValue; float calibrationValue;
// NVS lesen // NVS lesen
preferences.begin(NVS_NAMESPACE, true); // true = readonly preferences.begin(NVS_NAMESPACE, true); // true = readonly
calibrationValue = preferences.getLong(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue); calibrationValue = preferences.getFloat(NVS_KEY_CALIBRATION, defaultScaleCalibrationValue);
preferences.end(); preferences.end();
Serial.print("Read Scale Calibration Value "); Serial.print("Read Scale Calibration Value ");
@@ -68,6 +70,13 @@ uint8_t start_scale() {
if (isnan(calibrationValue) || calibrationValue < 1) { if (isnan(calibrationValue) || calibrationValue < 1) {
calibrationValue = defaultScaleCalibrationValue; calibrationValue = defaultScaleCalibrationValue;
scaleCalibrated = 0; scaleCalibrated = 0;
oledShowMessage("Scale not calibrated!");
for (uint16_t i = 0; i < 50000; i++) {
yield();
vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset();
}
} }
oledShowMessage("Scale Tare Please remove all"); oledShowMessage("Scale Tare Please remove all");
@@ -90,7 +99,7 @@ uint8_t start_scale() {
BaseType_t result = xTaskCreatePinnedToCore( BaseType_t result = xTaskCreatePinnedToCore(
scale_loop, /* Function to implement the task */ scale_loop, /* Function to implement the task */
"ScaleLoop", /* Name of the task */ "ScaleLoop", /* Name of the task */
10000, /* Stack size in words */ 2048, /* Stack size in words */
NULL, /* Task input parameter */ NULL, /* Task input parameter */
scaleTaskPrio, /* Priority of the task */ scaleTaskPrio, /* Priority of the task */
&ScaleTask, /* Task handle. */ &ScaleTask, /* Task handle. */
@@ -101,20 +110,21 @@ uint8_t start_scale() {
} else { } else {
Serial.println("ScaleLoop-Task erfolgreich erstellt"); Serial.println("ScaleLoop-Task erfolgreich erstellt");
} }
return (scaleCalibrated == 1) ? 1 : 3;
} }
uint8_t calibrate_scale() { uint8_t calibrate_scale() {
long newCalibrationValue; uint8_t returnState = 0;
float newCalibrationValue;
vTaskSuspend(RfidReaderTask);
vTaskSuspend(ScaleTask);
//vTaskSuspend(RfidReaderTask);
vTaskDelete(RfidReaderTask);
pauseBambuMqttTask = true; pauseBambuMqttTask = true;
pauseMainTask = 1; pauseMainTask = 1;
if (scale.wait_ready_timeout(1000)) if (scale.wait_ready_timeout(1000))
{ {
scale.set_scale(); scale.set_scale();
oledShowMessage("Step 1 empty Scale"); oledShowMessage("Step 1 empty Scale");
@@ -136,7 +146,7 @@ uint8_t calibrate_scale() {
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
long newCalibrationValue = scale.get_units(10); float newCalibrationValue = scale.get_units(10);
Serial.print("Result: "); Serial.print("Result: ");
Serial.println(newCalibrationValue); Serial.println(newCalibrationValue);
@@ -149,28 +159,33 @@ uint8_t calibrate_scale() {
// Speichern mit NVS // Speichern mit NVS
preferences.begin(NVS_NAMESPACE, false); // false = readwrite preferences.begin(NVS_NAMESPACE, false); // false = readwrite
preferences.putLong(NVS_KEY_CALIBRATION, newCalibrationValue); preferences.putFloat(NVS_KEY_CALIBRATION, newCalibrationValue);
preferences.end(); preferences.end();
// Verifizieren // Verifizieren
preferences.begin(NVS_NAMESPACE, true); preferences.begin(NVS_NAMESPACE, true);
long verifyValue = preferences.getLong(NVS_KEY_CALIBRATION, 0); float verifyValue = preferences.getFloat(NVS_KEY_CALIBRATION, 0);
preferences.end(); preferences.end();
Serial.print("Verified stored value: "); Serial.print("Verified stored value: ");
Serial.println(verifyValue); Serial.println(verifyValue);
Serial.println("End calibration, revome weight"); Serial.println("End calibration, remove weight");
oledShowMessage("Remove weight"); oledShowMessage("Remove weight");
scale.set_scale(newCalibrationValue);
for (uint16_t i = 0; i < 2000; i++) { for (uint16_t i = 0; i < 2000; i++) {
yield(); yield();
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
oledShowMessage("Scale calibrated");
oledShowMessage("Calibration done"); // For some reason it is not possible to re-tare the scale here, it will result in a wdt timeout. Instead let the scale loop do the taring
//scale.tare();
scaleTareRequest = true;
for (uint16_t i = 0; i < 2000; i++) { for (uint16_t i = 0; i < 2000; i++) {
yield(); yield();
@@ -178,8 +193,9 @@ uint8_t calibrate_scale() {
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
//ESP.restart(); returnState = 1;
} }
else else
{ {
{ {
@@ -192,10 +208,10 @@ uint8_t calibrate_scale() {
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
return 0; returnState = 0;
} }
} }
} }
else else
{ {
Serial.println("HX711 not found."); Serial.println("HX711 not found.");
@@ -207,17 +223,13 @@ uint8_t calibrate_scale() {
vTaskDelay(pdMS_TO_TICKS(1)); vTaskDelay(pdMS_TO_TICKS(1));
esp_task_wdt_reset(); esp_task_wdt_reset();
} }
return 0; returnState = 0;
} }
oledShowMessage("Scale Ready"); vTaskResume(RfidReaderTask);
vTaskResume(ScaleTask);
Serial.println("starte Scale Task");
start_scale();
pauseBambuMqttTask = false; pauseBambuMqttTask = false;
pauseMainTask = 0; pauseMainTask = 0;
return 1; return returnState;
} }

View File

@@ -12,7 +12,7 @@ uint8_t tareScale();
extern HX711 scale; extern HX711 scale;
extern int16_t weight; extern int16_t weight;
extern uint8_t weigthCouterToApi; extern uint8_t weigthCouterToApi;
extern uint8_t scale_tare_counter; extern uint8_t scaleTareRequest;
extern uint8_t pauseMainTask; extern uint8_t pauseMainTask;
extern uint8_t scaleCalibrated; extern uint8_t scaleCalibrated;