Arduino Brausteuerung mit Relais

Antworten
grotbeck
Posting Junior
Posting Junior
Beiträge: 12
Registriert: Freitag 12. April 2024, 19:10

Arduino Brausteuerung mit Relais

#1

Beitrag von grotbeck »

Hallo zusammen,

ich baue aktuell immer noch an meiner kleinen DIY Arduino Brausteuerung. Das ganze war als ganz kleines Projekt gestartet und wird stetig erweitert. Zuletzt wurde mir beim der Motorsteuerung hier auch bestens weitergeholfen. Nun zu meiner Frage:

Ich habe ein 5V Arduino Relais angeschlossen und möchte dies nun über mein Python Programm steuern. Der Arduino soll über das Python Programm verbunden werden und Temperatur und Drehzahl Daten empfangen (funktioniert auch). Mit dem Button „Automatik starten“ soll nun die Brauautomatik gestartet werden, welche die Rasten und Zeiten aus dem Rezept ausliest. Dann soll das Relais bzw. Die Herdplatte ein und ausgeschaltet werden nach IST/SOLL abgleich der Temperaturen.
Das Relais schaltet auch schon korrekt, allerdings kann ich die Automatik nicht starten. Wenn ich auf den Button „Automatik starten“ klicke, passiert einfach nichts und ich finde den Fehler nicht. Vielleicht kann mir ein erfahrener Bastler weiterhelfen - Danke im Voraus!

Python Code

Code: Alles auswählen

 import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import csv
import serial
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

# Serielle Verbindung initialisieren
ser = None  # Globale Variable für die serielle Verbindung

def connect_to_arduino():
    global ser
    try:
        ser = serial.Serial('/dev/cu.usbserial-1420', 9600, timeout=1)
        if ser.is_open:
            connection_status_label.config(text="AKTIV", fg="green")
            messagebox.showinfo("Erfolg", "Verbindung hergestellt!")
            read_serial_data()
        else:
            messagebox.showerror("Fehler", "Serielle Verbindung konnte nicht hergestellt werden.")
    except serial.SerialException as e:
        connection_status_label.config(text="INAKTIV", fg="red")
        messagebox.showerror("Fehler", "Keine Verbindung möglich! Bitte schließen Sie die Brausteuerung an (Bluetooth oder USB)")

def read_serial_data():
    global temperature_data
    try:
        if ser and ser.is_open:
            data = ser.readline().decode().strip()
            if data.startswith("Temperatur:"):
                temp_value = float(data.split(":")[1].strip().split()[0])
                temperature_value_label.config(text=f"{temp_value} °C")
                temperature_data.append(temp_value)  # Temperaturdaten aktualisieren
                update_temperature_color(data)
            elif data.startswith("Drehzahl:"):
                speed_value = data.split(":")[1].strip()
                speed_value_label.config(text=f"{speed_value} ")
            root.after(100, read_serial_data)  # Funktion alle 100 ms erneut aufrufen
    except serial.SerialException as e:
        print("Fehler beim Lesen der seriellen Daten:", e)
        root.after(100, read_serial_data)  # Fehlerbehandlung: Funktion erneut aufrufen
        
def update_temperature_color(data):
    current_temp = float(data.split(":")[1].strip().split()[0])  # Temperatur aus den Daten extrahieren
    for entry in recipe_entries + special_recipe_entries:
        if entry["key"].cget("text") in ["Rast1-Grad", "Rast2-Grad", "Rast3-Grad", "Einmaischtemperatur", 
                                         "Abmaischtemperatur"]:
            temp_value = entry["value"].get("1.0", "end-1c")
            if temp_value and float(temp_value) < current_temp:
                entry["value"].config(fg="red")
            else:
                entry["value"].config(fg="green")
    

def load_recipe():
    file_path = filedialog.askopenfilename(filetypes=[("CSV files", "*.csv")])
    if file_path:
        with open(file_path, newline='') as file:
            recipe_data = csv.reader(file, delimiter=',')
            display_recipe(recipe_data)

def display_recipe(recipe_data):
    for entry in recipe_entries + special_recipe_entries:
        entry["value"].delete(1.0, tk.END)  # Löschen des vorherigen Inhalts
    for row in recipe_data:
        key = row[0]
        value = row[1]
        if key in special_recipe_labels:  # Überprüfen, ob es sich um spezielle Labels handelt
            for entry in special_recipe_entries:
                if entry["key"].cget("text") == key:
                    entry["value"].insert(tk.END, value)
                    break
        else:
            for entry in recipe_entries:
                if entry["key"].cget("text") == key:
                    entry["value"].insert(tk.END, value)
                    break

def save_recipe():
    file_path = filedialog.asksaveasfilename(defaultextension=".csv", filetypes=[("CSV files", "*.csv")])
    if file_path:
        with open(file_path, 'w', newline='') as file:
            writer = csv.writer(file)
            for entry in recipe_entries:
                writer.writerow([entry["key"].cget("text"), entry["value"].get("1.0", "end-1c")])
            for entry in special_recipe_entries:
                writer.writerow([entry["key"].cget("text"), entry["value"].get("1.0", "end-1c")])

def start_automatic_mode():
    global automatic_mode
    if ser is None or not ser.is_open:
        messagebox.showerror("Fehler", "Keine Verbindung zum Arduino. Bitte stellen Sie sicher, dass der Arduino verbunden ist.")
        return
    
    target_temp_rast1 = float(special_recipe_entries[0]["value"].get("1.0", "end-1c"))  # Zieltemperatur für Rast1 aus dem Formularfeld "Rast1-Grad" lesen
    duration_rast1 = int(special_recipe_entries[1]["value"].get("1.0", "end-1c"))  # Dauer für Rast1 aus dem Formularfeld "Rast1-Min" lesen
    ser.write(f"Rast1:{target_temp_rast1}:{duration_rast1}\n".encode())  # Befehl an den Arduino senden
    
    target_temp_rast2 = float(special_recipe_entries[2]["value"].get("1.0", "end-1c"))  # Zieltemperatur für Rast2 aus dem Formularfeld "Rast2-Grad" lesen
    duration_rast2 = int(special_recipe_entries[3]["value"].get("1.0", "end-1c"))  # Dauer für Rast2 aus dem Formularfeld "Rast2-Min" lesen
    ser.write(f"Rast2:{target_temp_rast2}:{duration_rast2}\n".encode())  # Befehl an den Arduino senden
    
    target_temp_abmaisch = float(special_recipe_entries[4]["value"].get("1.0", "end-1c"))  # Zieltemperatur für Abmaischtemperatur aus dem Formularfeld "Abmaischtemperatur" lesen
    ser.write(f"Abmaisch:{target_temp_abmaisch}\n".encode())  # Befehl an den Arduino senden
    automatic_mode = True  # Automatikmodus aktivieren


def stop_automatic_mode():
    global automatic_mode
    # Befehl an den Arduino senden, um den Automatikmodus zu beenden
    ser.write("stop_auto\n".encode())  
    automatic_mode = False  # Automatikmodus deaktivieren



def plot_temperature_graph():
    global temperature_data
    if len(temperature_data) < 2:
        messagebox.showerror("Fehler", "Es sind nicht genügend Temperaturdaten vorhanden, um ein Diagramm zu zeichnen.")
        return

    # Extrahieren der Werte aus den Textboxen und ihre Label
    temperature_values = []
    temperature_labels = []
    for entry in special_recipe_entries:
        label_text = entry["key"].cget("text")
        if label_text in ["Rast1-Grad", "Rast2-Grad", "Rast3-Grad", "Einmaischtemperatur", "Abmaischtemperatur"]:
            temp_value = entry["value"].get("1.0", "end-1c")
            if temp_value:
                temperature_values.append(float(temp_value))
                temperature_labels.append(label_text)

    # Erstellen des Diagramms
    fig, ax = plt.subplots(figsize=(8, 6))
    minutes = range(0, len(temperature_data) * 5, 5)  # Erstellt eine Liste von 0 bis zur Länge der Daten * 5 (in Minuten)
    ax.plot(minutes, temperature_data, color='blue')

    # Einfügen der Werte aus den Textboxen in die Y-Achsen-Daten und Anmerkungen hinzufügen
    for i, temp_value in enumerate(temperature_values):
        ax.axhline(y=temp_value, color='red', linestyle='--')
        # Hinzufügen eines weißen Hintergrunds hinter dem Labeltext
        bbox_props = dict(boxstyle="square,pad=0.3", fc="white", ec="white", lw=0)
        ax.text((len(temperature_data) - 1) * 5, temp_value, temperature_labels[i], color='red', fontsize=10, ha='right', va='center', bbox=bbox_props)

    # Einstellen der Achsenbeschriftungen und Titel
    ax.set_xlabel('Zeit (Minuten)')
    ax.set_ylabel('Temperatur (°C)')
    ax.set_title('Temperaturverlauf')

    # Einstellen der Achsenskalen
    ax.set_xlim(0, (len(temperature_data) - 1) * 5)  # x-Achse: 0 bis (Anzahl der Datenpunkte * 5 - 5)
    ax.set_ylim(0, 100)  # y-Achse: 0 bis 100 Grad Celsius

    # Hinzufügen eines Rasters
    ax.grid(True)

    # Funktion zum Aktualisieren des Diagramms alle 5 Sekunden aufrufen
    def update_plot(ax):
        ax.clear()
        minutes = range(0, len(temperature_data) * 5, 5)  # Erstellt eine Liste von 0 bis zur Länge der Daten * 5 (in Minuten)
        ax.plot(minutes, temperature_data, color='blue')

        # Einfügen der Werte aus den Textboxen in die Y-Achsen-Daten und Anmerkungen hinzufügen
        for i, temp_value in enumerate(temperature_values):
            ax.axhline(y=temp_value, color='red', linestyle='--')
            bbox_props = dict(boxstyle="square,pad=0.3", fc="white", ec="white", lw=0)
            ax.text((len(temperature_data) - 1) * 5, temp_value, temperature_labels[i], color='red', fontsize=10, ha='right', va='center', bbox=bbox_props)

        ax.set_xlabel('Zeit (Minuten)')
        ax.set_ylabel('Temperatur (°C)')
        ax.set_title('Temperaturverlauf')
        ax.set_xlim(0, (len(temperature_data) - 1) * 5)
        ax.set_ylim(0, 100)
        ax.grid(True)
        graph_canvas.draw()
        graph_window.after(5000, update_plot, ax)  # Alle 5 Sekunden aktualisieren

    # Anzeigen des Diagramms in einem separaten Fenster
    graph_window = tk.Toplevel(root)
    graph_window.title("Temperaturverlauf")
    graph_canvas = FigureCanvasTkAgg(fig, master=graph_window)
    graph_canvas.draw()
    graph_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

    # Erste Aktualisierung des Diagramms starten
    update_plot(ax)

def show_info():
    info_window = tk.Toplevel(root)
    info_window.title("Info")
    info_text = "Version 1.0\nCopyright by Jonas Dasbeck"
    info_label = tk.Label(info_window, text=info_text, font=("Helvetica", 12), padx=10, pady=10)
    info_label.pack()


# GUI erstellen
root = tk.Tk()
root.title("Grotbeck Brausteuerung")
root.geometry("1000x700")  # Größe des Fensters

# Variablen für Temperaturdaten und Labels
temperature_data = []

# Rahmen um den Arduino-Teil
arduino_frame = ttk.LabelFrame(root, text="Dashboard", borderwidth=2, relief="groove")
arduino_frame.pack(padx=10, pady=10, fill="both", expand=True)

# Labels für die Verbindung
connection_label_text = tk.Label(arduino_frame, text="Verbindung:", font=("Helvetica", 24), fg="black")
connection_label_text.grid(row=0, column=4, padx=(10, 5))

connection_status_label = tk.Label(arduino_frame, text="INAKTIV", font=("Digital-7", 36), fg="red", bg="black")
connection_status_label.grid(row=0, column=5, padx=(0, 10))

# Labels für Temperatur und Drehzahl
temperature_label_text = tk.Label(arduino_frame, text="Temperatur:", font=("Helvetica", 24), fg="black")
temperature_label_text.grid(row=0, column=0, padx=(10, 5))

temperature_value_label = tk.Label(arduino_frame, text="0.0 °C", font=("Digital-7", 36), fg="red", bg="black")
temperature_value_label.grid(row=0, column=1, padx=(0, 10))

speed_label_text = tk.Label(arduino_frame, text="Drehzahl:", font=("Helvetica", 24), fg="black")
speed_label_text.grid(row=0, column=2, padx=(10, 5))

speed_value_label = tk.Label(arduino_frame, text="0 U/min", font=("Digital-7", 36), fg="red", bg="black")
speed_value_label.grid(row=0, column=3, padx=(0, 10))

# Rahmen für Rezeptinformationen
recipe_area_frame = ttk.LabelFrame(root, text="Allgemeines", borderwidth=2, relief="groove")
recipe_area_frame.pack(padx=10, pady=10, fill="both", expand=True)

# Rahmen für den Rezeptbereich
recipe_frame = ttk.Frame(recipe_area_frame)
recipe_frame.pack(side="left", fill="both", expand=True)

# Rahmen für spezielle Rezeptinformationen
special_recipe_frame = ttk.LabelFrame(recipe_area_frame, text="Temperaturen und Zeiten", borderwidth=2, relief="groove")
special_recipe_frame.pack(side="left", padx=10, pady=10, fill="both", expand=True)

# Textfelder für die Rezeptinformationen
recipe_entries = []
recipe_labels = ["Version", "Name", "Datum", "Sorte", "Ausschlagwuerze",
                "Sudhausausbeute", "Stammwuerze", "Bittere", "Farbe", "Alkohol", "Kurzbeschreibung",
                "Malze", "Maischform"]
row = 0
for label in recipe_labels:
    key_label = tk.Label(recipe_frame, text=label, font=("Helvetica", 12))
    key_label.grid(row=row, column=0, sticky="w")
    if label == "Malze":
        value_entry = tk.Text(recipe_frame, width=30, height=3, wrap="word", font=("Helvetica", 12))
    else:
        value_entry = tk.Text(recipe_frame, width=30, height=1, wrap="word", font=("Helvetica", 12))
    value_entry.grid(row=row, column=1, padx=5, pady=5, sticky="w")
    recipe_entries.append({"key": key_label, "value": value_entry})
    row += 1

# Textfelder für die speziellen Rezeptinformationen
special_recipe_entries = []
special_recipe_labels = ["Rast1-Grad", "Rast1-Min", "Rast2-Grad", "Rast2-Min",
                         "Rast3-Grad", "Rast3-Min", "Einmaischtemperatur", "Abmaischtemperatur",
                         "Nachguss", "Kochzeit_Wuerze", "Hopfenkochen", "Hefe", "Gaertemperatur",
                         "Karbonisierung"]
row = 0
for label in special_recipe_labels:
    key_label = tk.Label(special_recipe_frame, text=label, font=("Helvetica", 12))
    key_label.grid(row=row, column=0, sticky="w")
    value_entry = tk.Text(special_recipe_frame, width=30, height=1, wrap="word", font=("Helvetica", 12))
    value_entry.grid(row=row, column=1, padx=5, pady=5, sticky="w")
    special_recipe_entries.append({"key": key_label, "value": value_entry})
    row += 1

# Buttons
buttons_frame = ttk.Frame(root)
buttons_frame.pack(padx=10, pady=10, fill="x")

# Button zum Laden eines Rezepts
load_recipe_button = tk.Button(buttons_frame, text="Rezept laden", command=load_recipe)
load_recipe_button.pack(side="left", padx=5)

# Button zum Verbinden mit Arduino
connect_button = tk.Button(buttons_frame, text="Verbinden", command=connect_to_arduino)
connect_button.pack(side="left", padx=5)

# Button zum Ändern und Speichern des Rezepts
save_recipe_button = tk.Button(buttons_frame, text="Rezept ändern", command=save_recipe)
save_recipe_button.pack(side="left", padx=5)

# Button zum Anzeigen des Temperaturdiagramms
show_temperature_graph_button = tk.Button(buttons_frame, text="Temperatur Diagram", command=plot_temperature_graph)
show_temperature_graph_button.pack(side="left", padx=5)

# Button zum Anzeigen von Informationen
info_button = tk.Button(buttons_frame, text="Info", command=show_info)
info_button.pack(side="left", padx=5)

# Buttons
start_auto_button = tk.Button(buttons_frame, text="Automatik starten", command=start_automatic_mode)
start_auto_button.pack(side="left", padx=5)

stop_auto_button = tk.Button(buttons_frame, text="Automatik beenden", command=stop_automatic_mode)
stop_auto_button.pack(side="left", padx=5)

# GUI starten
root.mainloop()
Arduino Sketch

Code: Alles auswählen

 #include <TM1637Display.h>
#include <OneWire.h>
#include <DallasTemperature.h>

#define ONE_WIRE_BUS 7 // Pin für den DS18B20-Sensor
#define CLK 3 // Pin CLK des TM1637-Displays
#define DIO 4 // Pin DIO des TM1637-Displays
#define RELAY_PIN 10 // Pin für das Relais
#define SPEED_PIN 2 // Pin für den SPEED-Ausgang vom Treiber (Interrupt-Pin)
#define ALARM_PIN 6 // Pin für den ALM-Ausgang vom Treiber
#define MOTOR_POLE_PAIRS 6 // Anzahl der Motorpolpaare
#define TIME_WINDOW 5000 // Zeitfenster in Millisekunden (5 Sekunden)


volatile int pulseCount = 0; // Variable für die Anzahl der Pulsen
unsigned long startTime = 0; // Zeitstempel für den Start des Zeitfensters

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
TM1637Display display(CLK, DIO);

enum ProcessState {
  IDLE,
  RAST_1,
  RAST_2,
  ABMAISCH,
  FINISHED
};

ProcessState current_process_state = IDLE;
unsigned long start_temperature_reached_time = 0;
float target_temp_rast1 = 0;
int duration_rast1 = 0;
float target_temp_rast2 = 0;
int duration_rast2 = 0;
float target_temp_abmaisch = 0;
bool automatic_mode = false;

void setup() {
  Serial.begin(9600);
  sensors.begin();
  display.setBrightness(0x0a); // Helligkeit einstellen (0-0x0f)
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(2), countPulses, RISING);
  startTime = millis(); // Initialisierung des Zeitstempels für den Start
}

void loop() {
  // Auslesen der Temperatur und Anzeige
  sensors.requestTemperatures(); // Temperatur aktualisieren
  float tempC = sensors.getTempCByIndex(0); // Temperatur in Celsius lesen
  if (tempC != DEVICE_DISCONNECTED_C) { // Überprüfen, ob ein gültiger Wert gelesen wurde
    Serial.print("Temperatur: ");
    Serial.print(tempC);
    Serial.println(" °C");
    int temp = int(tempC); // Temperatur auf ganze Zahl konvertieren
    display.showNumberDec(temp); // Temperatur ohne Dezimalstelle auf dem Display anzeigen
  } else {
    Serial.println("Fehler beim Lesen der Temperatur");
    display.showNumberDec(8888); // Anzeige auf "8888" setzen, wenn ein Fehler auftritt
  }

  // Automatikmodus: Logik für die verschiedenen Phasen des Brauvorgangs
  if (automatic_mode) {
    switch (current_process_state) {
      case RAST_1:
        if (tempC >= target_temp_rast1 && millis() - start_temperature_reached_time >= duration_rast1 * 60000) {
          // Rast 1 abgeschlossen
          current_process_state = RAST_2;
          start_temperature_reached_time = millis();
          digitalWrite(RELAY_PIN, LOW); // Relais ausschalten
          Serial.println("Rast 1 abgeschlossen");
        }
        break;
      case RAST_2:
        if (tempC >= target_temp_rast2 && millis() - start_temperature_reached_time >= duration_rast2 * 60000) {
          // Rast 2 abgeschlossen
          current_process_state = ABMAISCH;
          start_temperature_reached_time = millis();
          digitalWrite(RELAY_PIN, LOW); // Relais ausschalten
          Serial.println("Rast 2 abgeschlossen");
        }
        break;
      case ABMAISCH:
        if (tempC >= target_temp_abmaisch) {
          // Abmaischtemperatur erreicht
          // Hier könnten Sie eine Benachrichtigung senden oder weitere Aktionen ausführen
        }
        break;
      case FINISHED:
        // Brauvorgang abgeschlossen
        break;
      default:
        break;
    }
  }

  // Berechnung und Anzeige der Motordrehzahl
  if (pulseCount == 0) {
    startTime = millis(); // Zeitstempel für den Start des Zeitfensters aktualisieren
  }
  unsigned long elapsedTime = millis() - startTime; // Verstrichene Zeit seit dem Start des Zeitfensters
  float F = pulseCount / (float)elapsedTime; // Pulse pro Millisekunde berechnen
  float motorSpeed = (F * 60000.0 / MOTOR_POLE_PAIRS) / 2; // Drehzahl in U/min berechnen und halbieren
  pulseCount = 0; // Pulszähler für das nächste Zeitfenster zurücksetzen
  startTime = millis(); // Aktualisieren des Startzeitpunkts für das nächste Zeitfenster
  Serial.print("Drehzahl: ");
  Serial.print((int)motorSpeed); // Drehzahl als Ganzzahl ohne Dezimalstellen ausgeben
  Serial.println(" U/min");

  // andere Teile des Codes hier...
}

void startRast1(float target_temp, int duration) {
  target_temp_rast1 = target_temp;
  duration_rast1 = duration;
  current_process_state = RAST_1;
  start_temperature_reached_time = millis();
  digitalWrite(RELAY_PIN, HIGH); // Relais einschalten
}

void startRast2(float target_temp, int duration) {
  target_temp_rast2 = target_temp;
  duration_rast2 = duration;
  current_process_state = RAST_2;
  start_temperature_reached_time = millis();
  digitalWrite(RELAY_PIN, HIGH); // Relais einschalten
}

void startAbmaisch(float target_temp) {
  target_temp_abmaisch = target_temp;
  current_process_state = ABMAISCH;
  start_temperature_reached_time = millis();
  digitalWrite(RELAY_PIN, HIGH); // Relais einschalten
}

void countPulses() {
  if (pulseCount == 0) {
    startTime = millis(); // Zeitstempel für den Start des Zeitfensters aktualisieren
  }
  pulseCount++; // Inkrementieren der Pulszählung
}
Antworten