kugelmuehle/kugelmuehle.ino

464 lines
13 KiB
Arduino
Raw Normal View History

2026-09-23 23:03:07 +02:00
#include <Arduino.h>
#include <types.h>
#include <display_ui.h>
const char* ssid = "Stockfeld";
const char* password = "70691554384740972413";
2026-09-25 01:19:16 +02:00
const char* DISCORD_WEBHOOK = "https://discord.com/api/webhooks/1552818996582285372/ChwhTOdajIwIVbdIpweSorYauwGHJTjjzWNGCjzPw1mh31-snlynh0ZW1InSar_aV8IB";
2026-09-23 23:03:07 +02:00
// NTP-Server & Zeitzone für Deutschland
const char* ntpServer = "pool.ntp.org";
const char* tzInfo = "CET-1CEST,M3.5.0,M10.5.0/3";
FastAccelStepperEngine engine = FastAccelStepperEngine();
FastAccelStepper *stepper = NULL;
// ################ Variabeln ################
const uint32_t MAX_SPEED_HZ = 106000;
const int32_t COLDTEMP_C = 50;
bool clickPlus = 0;
bool clickMinus = 0;
bool clickStop = 0;
bool holdPlus = 0;
bool holdMinus = 0;
bool holdStop = 0;
bool shouldRun = true;
int lastSpeed = 0;
int32_t lastControl = 0;
uint32_t lastPlotTime = 0;
bool autoStop = false;
bool bSettings = false;
uint32_t curTemp = 0;
2026-09-25 01:19:16 +02:00
bool send = false;
bool stopMove = false;
2026-09-23 23:03:07 +02:00
struct tm timeinfo;
TimeEditStep timeEditStep = EDIT_NONE;
// fest in den Speicher schreiben
Preferences prefs;
SystemSettings settings;
//#####################################
//########### Hilfsfunktion ###########
//#####################################
// Überladung des Präfix-Operators (++timeEditStep)
TimeEditStep& operator++(TimeEditStep& step) {
step = (step == EDIT_END_MIN) ? EDIT_NONE : static_cast<TimeEditStep>(step + 1);
return step;
}
// Werte aus dem Flash speicher laden
void loadSettings() {
// Namespace öffnen (Name max. 15 Zeichen, "true" = read-only)
prefs.begin("timer_cfg", true);
// Werte auslesen
settings.startHour = prefs.getUChar("s_hour", 0);
settings.startMin = prefs.getUChar("s_min", 0);
settings.endHour = prefs.getUChar("e_hour", 0);
settings.endMin = prefs.getUChar("e_min", 0);
settings.timerEnabled = prefs.getBool("t_on", false);
settings.maxTemp = prefs.getShort("max_temp", 70);
2026-09-25 01:19:16 +02:00
settings.accelRate = prefs.getInt("accel_rate", 20000);
2026-09-23 23:03:07 +02:00
prefs.end();
Serial.println("[NVS] Einstellungen erfolgreich geladen.");
}
// Save Settings in Flash Speichern
void saveSettings() {
prefs.begin("timer_cfg", false); // "false" = Read/Write
prefs.putUChar("s_hour", settings.startHour);
prefs.putUChar("s_min", settings.startMin);
prefs.putUChar("e_hour", settings.endHour);
prefs.putUChar("e_min", settings.endMin);
prefs.putBool("t_on", settings.timerEnabled);
prefs.putShort("max_temp", settings.maxTemp);
prefs.putInt("accel_rate", settings.accelRate);
prefs.end();
Serial.println("[NVS] Einstellungen im Flash gespeichert.");
}
void initWiFiAndTime() {
WiFi.begin(ssid, password);
// Zeitzone und NTP-Server setzen
configTzTime(tzInfo, ntpServer);
// Warten auf Verbindung (optional mit Timeout)
int retries = 0;
while (WiFi.status() != WL_CONNECTED && retries < 20) {
delay(500);
retries++;
}
}
2026-09-25 01:19:16 +02:00
void sendDiscordNotification(const String& message) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[Discord] Nicht verbunden mit WiFi.");
return;
}
WiFiClientSecure client;
client.setInsecure(); // Überspringt die rechenintensive Zertifikatsketten-Prüfung
HTTPClient http;
if (http.begin(client, DISCORD_WEBHOOK)) {
http.addHeader("Content-Type", "application/json");
// JSON-Payload: {"content": "Deine Nachricht"}
String payload = "{\"content\": \"" + message + "\"}";
int httpResponseCode = http.POST(payload);
if (httpResponseCode == 204 || httpResponseCode == 200) {
Serial.println("[Discord] Nachricht erfolgreich gesendet.");
} else {
Serial.printf("[Discord] Fehler beim Senden: HTTP %d\n", httpResponseCode);
}
http.end();
}
}
2026-09-23 23:03:07 +02:00
//#######################################################
//####################### Setup #########################
//#######################################################
void setup() {
Serial.begin(115200);
delay(1000); // Zeit für USB-CDC-Verbindung geben
// Daten aus Flash speicher laden
loadSettings();
engine.init();
stepper = engine.stepperConnectToPin(stepPinStepper);
if (stepper) {
stepper->setDirectionPin(dirPinStepper);
stepper->setEnablePin(enablePinStepper, true);
stepper->setAutoEnable(true);
stepper->setSpeedInHz(MAX_SPEED_HZ);
}
// Pin Config
pinMode(buttonPlus, INPUT_PULLUP);
pinMode(buttonMinus, INPUT_PULLUP);
pinMode(buttonStop, INPUT_PULLUP);
initWiFiAndTime();
initDisplay();
// Start Animation
}
bool getCurrentTime(int &hour, int &minute, int &second) {
if (!getLocalTime(&timeinfo, 50)) { // 50ms Timeout
return false; // Noch keine NTP-Zeit synchronisiert
}
hour = timeinfo.tm_hour;
minute = timeinfo.tm_min;
second = timeinfo.tm_sec;
return true;
}
// Rückgabe von Flanken (Klick) und Pegel (Halten)
void getInputs(bool &clickPlus, bool &clickMinus, bool &clickStop,
bool &holdPlus, bool &holdMinus, bool &holdStop) {
// Physische Pegel einlesen (Active-Low)
bool curPlus = (digitalRead(buttonPlus) == LOW);
bool curMinus = (digitalRead(buttonMinus) == LOW);
bool curStop = (digitalRead(buttonStop) == LOW);
// Speicher für vorherigen Durchlauf
static bool lastPlus = false;
static bool lastMinus = false;
static bool lastStop = false;
// Nur bei Veränderung true (Sperre)
clickPlus = (curPlus && !lastPlus);
clickMinus = (curMinus && !lastMinus);
clickStop = (curStop && !lastStop);
// Dauerzustand (für Stepper-Handbetrieb)
holdPlus = curPlus;
holdMinus = curMinus;
holdStop = curStop;
// Zustand für nächsten Durchlauf merken
lastPlus = curPlus;
lastMinus = curMinus;
lastStop = curStop;
}
// Liefert die Aktuelle Temperatur in °C
int getTempInC(uint8_t senTemp){
int milliVolt = analogReadMilliVolts(senTemp);
return ((milliVolt - 500)/10);
}
// Liefert true wenn der Timer aktuell Active ist und false wenn nicht aktiv
bool isTimerActiveNow(int curH, int curM, int startH, int startM, int endH, int endM) {
int current = curH * 60 + curM;
int start = startH * 60 + startM;
int end = endH * 60 + endM;
// Fall A: Zeitfenster am selben Tag (z. B. 08:00 bis 16:00)
if (start <= end) {
return (current >= start && current < end);
}
// Fall B: Zeitfenster über Mitternacht (z. B. 22:00 bis 04:00)
else {
return (current >= start || current < end);
}
}
// Die Funktion behandelt das gesammte Einstellungs menu
void setting(){
//######### Timer Edit #########
if (timeEditStep != EDIT_NONE) {
//PLUS GEDRÜCKT: Wert erhöhen
2026-09-25 01:19:16 +02:00
if (clickPlus) {
2026-09-23 23:03:07 +02:00
switch (timeEditStep) {
case EDIT_START_HOUR:
settings.startHour = (settings.startHour + 1) % 24;
break;
case EDIT_START_MIN:
settings.startMin = (settings.startMin + 5) % 60;
break;
case EDIT_END_HOUR:
settings.endHour = (settings.endHour + 1) % 24;
break;
case EDIT_END_MIN:
settings.endMin = (settings.endMin + 5) % 60;
break;
default: break;
}
}
// MINUS GEDRÜCKT: Wert verringern
if (clickMinus) {
switch (timeEditStep) {
case EDIT_START_HOUR:
settings.startHour = (settings.startHour == 0) ? 23 : settings.startHour - 1;
break;
case EDIT_START_MIN:
settings.startMin = (settings.startMin == 0) ? 59 : settings.startMin -5;
break;
case EDIT_END_HOUR:
settings.endHour = (settings.endHour == 0) ? 23 : settings.endHour - 1;
break;
case EDIT_END_MIN:
settings.endMin = (settings.endMin == 0) ? 59 : settings.endMin - 5;
break;
default: break;
}
}
// STOP GEDRÜCKT: Zum nächsten Wert springen
if (clickStop) {
++timeEditStep;
}
return; // Wichtig: Rest der Funktion überspringen, solange editiert wird!
}
//######### Max Temp Edit #########
if(settings.editMaxTemp){
if(clickPlus){
settings.maxTemp = (settings.maxTemp + 5) % 140;
}
2026-09-25 01:19:16 +02:00
if(clickMinus){
2026-09-23 23:03:07 +02:00
settings.maxTemp = (settings.maxTemp == 0) ? -20 : settings.maxTemp - 5;
}
if(clickStop){
settings.editMaxTemp = false;
}
return; // Wichtig: Rest der Funktion überspringen, solange editiert wird!
}
//######### Beschleunigung Edit #########
if(settings.editAcc){
if(clickPlus){
settings.accelRate = (settings.accelRate + 1000) % 20000;
}
2026-09-25 01:19:16 +02:00
if(clickMinus){
2026-09-23 23:03:07 +02:00
settings.accelRate = (settings.accelRate == 0) ? 0 : settings.accelRate - 1000;
}
if(clickStop){
settings.editAcc = false;
}
return; // Wichtig: Rest der Funktion überspringen, solange editiert wird!
}
//######### Menü #########
// Menü bewegen
if(clickPlus){
2026-09-25 01:19:16 +02:00
if(settings.settingIdx <= 3){
2026-09-23 23:03:07 +02:00
settings.settingIdx++;
}
}
if(clickMinus){
if(settings.settingIdx >=1){
settings.settingIdx--;
}
}
// Stop zum bestätigen
if (clickStop) {
if (settings.settingIdx == 0) {
// Zeitschaltuhr-Einstellung starten
timeEditStep = EDIT_START_HOUR;
}
else if (settings.settingIdx == 1) {
// Zeitschaltuhr ON/OFF
settings.timerEnabled = !settings.timerEnabled;
}else if (settings.settingIdx == 2) {
// Temperatur Grenze einstellen
settings.editMaxTemp = true;
}else if (settings.settingIdx == 3) {
// Beschleunigung
settings.editAcc = true;
}else if (settings.settingIdx == 4) {
// Menü verlassen
bSettings = false;
// Einstellungen Speichern
saveSettings();
}
}
}
void printStatus(FastAccelStepper *stepper, int speed, int clickPlus, int clickMinus){
// Plotter-Ausgabe alle 50 ms (überflutet den Bus nicht)
if (millis() - lastPlotTime >= 50) {
lastPlotTime = millis();
2026-09-25 01:19:16 +02:00
int32_t currentSpeed_HZ = stepper->getCurrentSpeedInUs() == 0 ? 0 : (1000000 / stepper->getCurrentSpeedInUs());
int32_t currentSpeed = (currentSpeed_HZ * 60)/1600;
2026-09-23 23:03:07 +02:00
// Format kompatibel für den Arduino Serial Plotter
Serial.print("Current_Speed_U/Min:");
Serial.print((60*currentSpeed)/3200);
Serial.print(" Accel_Control:");
Serial.print(speed);
Serial.print(" Btn_Up:");
Serial.print(clickPlus ? 5000 : 0);
Serial.print(" Btn_Down:");
Serial.print(clickMinus ? -5000 : 0);
Serial.println();
}
}
//#############################################################
//#################### MAIN ###################################
//#############################################################
void loop() {
int32_t speed = 0;
if (!stepper) {
Serial.println("[HALT] Kein Stepper-Objekt aktiv.");
delay(1000);
return;
}
// Inputs
getInputs(clickPlus, clickMinus, clickStop, holdPlus, holdMinus, holdStop);
curTemp = getTempInC(senTemp);
// zeitschaltuhr prüfen
int curH, curM, curS;
if (settings.timerEnabled && getCurrentTime(curH, curM, curS)) {
shouldRun = isTimerActiveNow(curH, curM, settings.startHour, settings.startMin, settings.endHour, settings.endMin);
}
2026-09-25 01:19:16 +02:00
if(curTemp >= settings.maxTemp){
string message = "Kugelmühle überhitzt! Temp = " + String(curTemp) + " °C";
sendDiscordNotification(message);
send = true;
}else{
send = false;
}
// Not-Stopp aktivieren bei Überhitzung ODER Zeitschaltuhr aus
2026-09-23 23:03:07 +02:00
if(curTemp >= settings.maxTemp || !shouldRun){
autoStop = true;
2026-09-25 01:19:16 +02:00
// Entsperren NUR wenn abgekühlt UND Zeitschaltuhr aktiv
}else if(curTemp <= COLDTEMP_C && shouldRun){
2026-09-23 23:03:07 +02:00
autoStop = false;
}
2026-09-25 01:19:16 +02:00
// Zeitschaltuhr start
if(settings.timerEnabled && shouldRun){
// TODO
// 1. Ziel-Geschwindigkeit vorgeben (z. B. 3200 Steps/s = 120 U/min bei 1/8 Schritt)
//stepper->setSpeedInHz(3200);
// 2. Beschleunigungsrampe festlegen
//stepper->setAcceleration(settings.accelRate);
// 3. Dauerfahrt starten (beschleunigt automatisch bis zur Ziel-Geschwindigkeit)
// stepper->runForward(); // oder stepper->runBackward();
}
2026-09-23 23:03:07 +02:00
// Main Control
2026-09-25 01:19:16 +02:00
if(holdStop && clickPlus){
bSettings = true;
}
2026-09-23 23:03:07 +02:00
if(bSettings){
setting();
2026-09-25 01:19:16 +02:00
}else if(clickStop){
stopMove = true;
2026-09-23 23:03:07 +02:00
}else if(holdPlus){
speed = settings.accelRate;
}else if(holdMinus){
speed = -settings.accelRate;
}else{
speed = 0;
}
2026-09-25 01:19:16 +02:00
// STOPP
if(stopMove || autoStop){
// stopp befehl nur senden wenn sich der Motor bewegt
if (stepper->isRunning()) {
stepper->moveByAcceleration(10000, true);
stepper->stopMove();
}
// MOVE
}else if (speed != lastSpeed ) {
2026-09-23 23:03:07 +02:00
stepper->moveByAcceleration(speed, true);
lastSpeed = speed;
}
2026-09-25 01:19:16 +02:00
// Reset der Brems beschleunigung
if (!stepper->isRunning()) {
stepper->setAcceleration(settings.accelRate);
stopMove = false;
}
int32_t currentSpeed_HZ = stepper->getCurrentSpeedInUs() == 0 ? 0 : (1000000 / stepper->getCurrentSpeedInUs());
int32_t currentSpeed = (currentSpeed_HZ * 60)/1600;
2026-09-23 23:03:07 +02:00
bool timeIsValid = getCurrentTime(curH, curM, curS);
updateDisplay(bSettings, settings.settingIdx, timeEditStep, settings, currentSpeed, curH, curM, timeIsValid, curTemp);
// Status ausgabe auf Terminal
printStatus(stepper, speed, clickPlus, clickMinus);
}