Cocktailmaschine + Coin Acceptor

Hallo zusammen,

ich bin absoluter Neuling was arduino angeht…aber ich habe es geschafft eine Cocktailmaschine mit arduino zu bauen.

Jetzt soll es aber noch die Möglichkeit geben einen Münzeinwurf zu integrieren und da hören meine Kenntnisse dann gänzlich auf… von daher würde ich mich freuen wenn die Community mir hier weiterhelfen könnte.

Zu den Facts:
Benutzt wird ein Münzprüfer (Coin Acceptor)

ein 1602 LCD

Mein Wunsch wäre es das auf dem Display in der ersten Zeile steht z.B. Cocktail: 2,50 €
in der zweiten zeile dann die angezeigt wird wieviel der Benutzer bereits in den Münzprüfer eingeworfen hat. wenn der Betrag von 2,50€ eingeworfen wurde besteht erst die Möglichkeit die Cocktail maschine anzuwerfen.

Das Münzgerät ist bereits eingerichetet:
10 cent = 1 impuls
20 cent = 2 impulse
50 cent = 5 impulse
1 € = 10 impulse
2 € = 20 impulse

und meine Special Free Münze = 25 impulse

2,50€ = 25 impulse

Wenn der Gesamtbetrag, also 2,50€ verfügbar sind soll noch eine grüne LED blinken und der Benutzer hat die Möglichkeit die Cocktailmaschine in Gang zu setzen

Wäre schön wenn ich meinen Code der Cocktailmaschine einfach nur einfügen könnte.

Hier noch mein Code:

#include "AccelStepper.h"
#include "PololuMaestro.h"
#include "Configuration.h"

String serialBuffer = "";
String actions[TOTAL_ACTIONS];

int counter = 0;
int lastIndex = 0;

AccelStepper stepper(X_INTERFACE_TYPE, X_STEP_PIN, X_DIR_PIN); // Define a stepper and the pins it will use
MicroMaestro maestro(maestroSerial); // Define a servo controller

void setup() {
  Serial.begin(9600); // Serial port for debugging
  maestroSerial.begin(9600); // Servo controller
  Serial2.begin(9600); // Bluetooth module
  stepper.setMaxSpeed(X_MAX_SPEED); // Sets the maximum speed the X axis accelerate up to
  pinMode(X_ENDSTOP_PIN, INPUT_PULLUP); // Initialize endstop pin with the internal pull-up resistor enabled
  homeXAxis(); // Return the X axis to it's home position at the startup
}

void homeXAxis() {
  int endStopState = digitalRead(X_ENDSTOP_PIN);
  
  while (endStopState == HIGH) {
    stepper.moveTo(100);
    stepper.setSpeed(X_HOME_SPEED);
    stepper.runSpeed();
    endStopState = digitalRead(X_ENDSTOP_PIN);
  }

  stepper.moveTo(stepper.currentPosition() - 50);
  while (stepper.distanceToGo() != 0) {
    stepper.setSpeed(X_PARK_SPEED * -1);
    stepper.runSpeed();
  }

  endStopState = digitalRead(X_ENDSTOP_PIN);

  while (endStopState == HIGH) {
    stepper.moveTo(100);
    stepper.setSpeed(X_PARK_SPEED);
    stepper.runSpeed();
    endStopState = digitalRead(X_ENDSTOP_PIN);
  }
  stepper.setCurrentPosition(0);
}

void loop() {
  while(Serial2.available() > 0) {
    char ch = Serial2.read();
    serialBuffer += ch;
    if (ch == '\n') {
      for(int i=0; i<serialBuffer.length(); i++) {
        if(serialBuffer.substring(i, i+1) == ",") {
          actions[counter] = serialBuffer.substring(lastIndex, i);
          lastIndex = i + 1;
          counter++;
        }

        if(i == serialBuffer.length() - 1) {
          actions[counter] = serialBuffer.substring(lastIndex, i);
        }
      }

      for(int z=0; z<TOTAL_ACTIONS; z++) {
        if(actions[z] != "0") {
          parseInput(actions[z]);
        }
      }

      Serial2.println("H");

      for(int y=0; y<TOTAL_ACTIONS; y++) {
        actions[y] = "0";
      }
      
      serialBuffer = "";
      counter = 0;
      lastIndex = 0;      
    }
  }
}

void parseInput(String input) {
  input.trim();
  byte command = input.charAt(0);

  switch(command) {
    case 'H':
      homeXAxis();
      break;
    case 'X':
      moveXTo(input);
      break;
    case 'F':
      pour(input);
      break;
  }
}

void moveXTo(String input) {
  int pos = input.substring(1).toInt();
  
  Serial.print("X goes to: ");
  Serial.println(pos);
  
  Serial2.println(input);
  
  if(pos < 0 && pos >= X_MAX_POS) {
    stepper.setAcceleration(X_ACCELERATION);
    stepper.moveTo(pos);
      if(pos < stepper.currentPosition()) {
        stepper.setSpeed(-100);
      } else {
        stepper.setSpeed(100);
      }
    while(stepper.distanceToGo() != 0) {
      stepper.run(); 
    }
  } else {
    Serial.println("Position should be between -4995 and 0");
  }
}

void pour(String input) {
  int count = 0; // Pour counter
  int times = 0; // Times to pour
  int holdDuration = 0; // Time duration to hold dispenser in open position
  int waitDuration = 0; // Time duration to wait till next pour
  
  for(int z=0; z<input.length(); z++) {
    byte parameter = input.substring(z, z+1).charAt(0);

    switch(parameter) {
      case 'F':
        times = getParameterValue(input, z);
        break;
      case 'H':
        holdDuration = getParameterValue(input, z);
        break;
      case 'W':
        waitDuration = getParameterValue(input, z);
        break;
    }
  }

  if(holdDuration > 0 && waitDuration > 0) {
    for(int i=0; i<times; i++) {
      maestro.setSpeed(0, SERVO_RAISE_SPEED);
      maestro.setTarget(0, SERVO_MAX_POS);
      delay(holdDuration);
      maestro.setSpeed(0, SERVO_RELEASE_SPEED);
      maestro.setTarget(0, SERVO_MIN_POS);
      if(times - 1 > count) {
        delay(waitDuration);
      } else {
        delay(DELAY_BETWEEN_INGREDIENTS);
      }
      count++;
    }
  } else {
    Serial.println("Hold and wait duration parameters cannot be lower than or equal to 0");
  }
}

int getParameterValue(String input, int z) {
  for(int y=z+1; y<input.length(); y++) {
    if(input.substring(y, y+1) == " ") {
      return input.substring(z+1, y).toInt();
      break;
    }
    if(y == input.length() - 1) {
      return input.substring(z+1, y+1).toInt();
    }
  }
}

Dann musst Du die Impulse zählen, Wie schnell kommen die? Gibt es einen Geldrückgabe-/Abbruchknopf? Wie wird der ausgegeben?

Gruß Tommy

es gibt keine Geldrückgabe oder Abbruchknopf.

Was ist "Micro Maestro" ?

MicroMaestro scheint ein ServoController zu sein. Hier ne Beschreibung in Deutsch

ich habe einen Sketch gefunden der das kann, nur habe ich keine Ahnung wie ich jetzt beide sketch zusammen führen kann… wäre für mich sehr hilfreich wenn die Comunity hier helfen kann

 #define COIN_PIN 2

void setup()
{
  // Debugging output
  Serial.begin(9600);

  // set up the LCD's number of rows and columns:
  lcd.begin(16, 2);

  Serial.println("Ready...");
  
  pinMode(COIN_PIN, INPUT);
  attachInterrupt(0, coinISR, RISING);  // COIN wire connected to D2;
}


// total amount of money collected;
float money = 0.0;

// gets incremented by the ISR;
// gets reset when coin was recognized (after train of pulses ends);
volatile int pulses = 0;
volatile long timeLastPulse = 0;


// executed for every pulse;
void coinISR()
{
  pulses++;
  timeLastPulse = millis();
}


void loop()
{
  lcd.setCursor(0,0);
  lcd.print("Please put coins");
  lcd.setCursor(0,1);
  lcd.print("PAID $");
  lcd.print(money);

  long timeFromLastPulse = millis() - timeLastPulse;
  if (pulses > 0 && timeFromLastPulse > 200)
  {
    // sequence of pulses stopped; determine the coin type;
    if (pulses == 2)
    {
      Serial.println("Received dime (2 pulses)");
      money += .1;
    }
    else if (pulses == 5)
    {
      Serial.println("Received quarter (5 pulses)");
      money += .25;
    }
    else if (pulses == 10)
    {
      Serial.println("Received looney (10 pulses)");
      money += 1.0;
    }
    else if (pulses == 15)
    {
      Serial.println("Received tooney (15 pulses)");
      money += 2.0;
    }
    else
    {
      Serial.print("Unknown coin: ");
      Serial.print(pulses);
      Serial.println(" pulses");
    }

    pulses = 0;
  }
}

Hallo gogolog1905,

Ich habe da schon was für Dich vorbereitet. Jedoch muss zum endgültigen Zusammenfügen unbedingt wissen, Ob, und wie Du eine Rückmeldung bekommst, sobald der Cocktail fertig ist. Falls du nicht weisst wie das gemeldet wird, setze bitte den Link zum Bauprojekt der Cocktailmaschine hier ein. Ich gehe davon aus, dass Du da ein Projekt nachgebaut hast.

Ohne diese Info kann ich die beiden Codes nicht korrekt zusammenfügen, obwohl sie jetzt weitestgehend dafür vorbereitet sind.

Wäre lieb von Dir, wenn Du mene Frage baldigst beantworten könntest.

Hallo gogolog1905,
nach längerer Suche bin ich endlich auf das Projekt Deiner Cocktailmaschine gestossen. es handelt sich wohl um den Barbot. Nach intensivem Studium des Programmcodes sowie der Beschreibung und den Videos, ist es mir endlich gelungen, die Info zu finden, die mir sagt, wie ich die Maschine daraufhin abfragen kann, wann ein Cocktail fertig zur Entnahme steht und somit das nächste Getränk kassiert werden kann.

Die gewünschte grüne LED , die blinken soll sobald genügend Geld eingeworfen wurde, wird an Pin 13 und GND des Mega angeschlossen.
Sie leuchtet dauerhaft, wenn Der Münzprüfer Bereit zur Geldannahme ist, und Blinkt , sobald genügend Geld für ein Getränk eingeworfen wurde.

Wenn der optionale, aber empfohlene Service-Schalter an Pin 6 und GND des Megas angeschlossen ist,
Erlischt die grüne LED, sobald auf SERVICE geschaltet wird, da dann der Münzprüfer inaktiv ist.

Das 16 x 2 Display hab ich mal per I2C angebunden auf I2C Adresse 0x27.
Die Adresse musst Du evt. an dein Display anpassen. Soltest Du das I2C Modul zum Display nicht haben,
Kansst Du das Display auch anders (4-bit mode) oder so anschliessen. Musst Dich halt kundig machen.
Tutorials dazu gibt es im Netz genug.
Und hier der Sketch → ok, zu gross, dann eben als Anhang

LG Stefan

gogolog1905_CoctailMaker.ino (11 KB)

Und hier für alle:

#define ServiceSwitchPin 6 // Hier an Pin D6 und GND des Mega kannst Du einen Schalter
// (Schliesser) anschliessen, den Du versteckt am CoktailMaker
// anbringst.  Damit kannst Du für ServiceArbeiten und Tests
// den MünzPrüfer 'überbrücken' so dass die Maschine auf
// Befehle vom Handy auch ohne Geldeinwurf reagiert.
// Du kanst den Schalter aber auch weglassen, dann läuft der
// Cocktailmaker aber nur noch nach Geldeinwurf.
#define COIN_PIN 2         // Münzprüfer (CoinAcceptor) Signal #define spezialFree 10*cocktailCost
#define LED_PIN 13          // Grüne LED Münzprüfer
#define spezialFree 10*cocktailCost  // wird benötigt um die SpezialFree Münze dem Cocktailpreis anzupassen.

#include "AccelStepper.h"
#include "PololuMaestro.h"
#include "Configuration.h"
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

// Cocktail Preis festlegen
const float cocktailCost = 2.50;  // Kosten für einen Coktail. Bei ner Preisänderung hier anpassen
bool moneyin = false;       // true = eingeworfener Betrag = Kosten für 1 Drink (€ 2.50).

// CocktailMaker runs
bool makerRun = false;      // true = Cocktailmaker freigegeben.
bool CocktailFinishd = false;

// Display init
LiquidCrystal_I2C lcd(0x27, 16, 2);  // Display 16 x 2 , I2C an Adr. 0x27  Adresse must Du evt. ANPASSEN. Ebenso wenn Display nicht I2C
const byte customChar[] = {0x07, 0x08, 0x1E, 0x08, 0x1E, 0x08, 0x07, 0x00}; // €-Symbol f. LCD

String serialBuffer = "";
String actions[TOTAL_ACTIONS];

int counter = 0;
int lastIndex = 0;
byte greenState = 1;
// total amount of money collected;
float money = 0.0;

// Accelsteper + Servo init
AccelStepper stepper(X_INTERFACE_TYPE, X_STEP_PIN, X_DIR_PIN); // Define a stepper and the pins it will use
MicroMaestro maestro(maestroSerial); // Define a servo controller

void setup() {
  Serial.begin(9600); // Serial port for debugging
  lcd.begin();
  lcd.createChar(0, customChar);  //€ Zeichen ins Display-Rom schreiben
  lcd.home();
  lcd.write(0);
  Serial.println("Ready...");
  pinMode(COIN_PIN, INPUT);
  attachInterrupt(0, coinISR, RISING);      // COIN wire connected to D2;
  pinMode(ServiceSwitchPin, INPUT_PULLUP);  // Pin für den ServiceSwitch auf Input
  pinMode(LED_PIN, OUTPUT);
  maestroSerial.begin(9600); // Servo controller
  Serial2.begin(9600); // Bluetooth module
  stepper.setMaxSpeed(X_MAX_SPEED); // Sets the maximum speed the X axis accelerate up to
  pinMode(X_ENDSTOP_PIN, INPUT_PULLUP); // Initialize endstop pin with the internal pull-up resistor enabled
  homeXAxis(); // Return the X axis to it's home position at the startup
}
//-----------------------Setup end---------------------------


// ********** Funktionen **************
void blinker (unsigned int interval)                      
{
  static unsigned long last_ms = 0;
  static bool state = LOW;
  
  unsigned long ms = millis();
  
  if (ms - last_ms >= interval) {
    last_ms = ms;
    state = !state;
    digitalWrite(LED_PIN, state);
  }
}
//--------------blinker end-----------

void homeXAxis() {
  int endStopState = digitalRead(X_ENDSTOP_PIN);

  while (endStopState == HIGH) {
    stepper.moveTo(100);
    stepper.setSpeed(X_HOME_SPEED);
    stepper.runSpeed();
    endStopState = digitalRead(X_ENDSTOP_PIN);
  }

  stepper.moveTo(stepper.currentPosition() - 50);
  while (stepper.distanceToGo() != 0) {
    stepper.setSpeed(X_PARK_SPEED * -1);
    stepper.runSpeed();
  }

  endStopState = digitalRead(X_ENDSTOP_PIN);

  while (endStopState == HIGH) {
    stepper.moveTo(100);
    stepper.setSpeed(X_PARK_SPEED);
    stepper.runSpeed();
    endStopState = digitalRead(X_ENDSTOP_PIN);
  }
  stepper.setCurrentPosition(0);
  CocktailFinishd = true;
}
//-----------Home XAxis end---------------------

volatile int pulses = 0;          // gets incremented by the ISR;
volatile long timeLastPulse = 0;  // gets reset when coin was recognized (after train of pulses ends)

// executed for every pulse;
void coinISR()
{
  pulses++;
  timeLastPulse = millis();
}
//-----------ISR ende----------------------

void loop() {
  if(greenState < 2) digitalWrite(LED_PIN, greenState); // wenn blinker inaktiv grüne LED gemäss Status Schalten
  if (digitalRead(ServiceSwitchPin) == HIGH)
  {
    moneyin = MoneyIn();      // überprüfung ob Kaufbetreg bereits eingeworfen wurde
    if (moneyin == true)      // wenn ja
    {
      greenState = 2; // grüne LED wird vom Blinker geschaltet
      blinker(333);
      if (!makerRun)
      {
        digitalWrite(LED_PIN, 0); // Led erstmalkurz aus
        delay(333);
        lcd.setCursor(0, 0);
        lcd.print(" W\341hlen Sie nun ");
        lcd.setCursor(0, 1);
        lcd.print(" Ihren Cocktail ");
        Serial.println("Cocktail wählen");
        makerRun = true;
      }
      CocktailMaker(false);
      if(CocktailFinishd == true)  // cocktail is fertig
      {
        money = 0.0;               // Betrag einkassiert
        pulses = 0;                // Müzzähler auf null
        moneyin = false;           // Flag für bezahltn Cocktail zurücksetzen
        makerRun = false;          // Cocktail Zubereitung beendet
        CocktailFinishd = false;   // Flag zurücksetzen , Münzprüfer wieder freigeben
        greenState = 1;            // grüne LED Dauerleuchten
        lcd.clear();   
      } 
    }
  }
  else
  {
    greenState = 0;
    bool service = CocktailMaker(true);
  }
}
//----------------------------loop ende-------------------------------

//----------------------------COIN ACCEPT------------------------------
bool MoneyIn()
{
  if (makerRun == true)
  {
    return true;
  }
  if (money == 0.0)
  {
    lcd.setCursor(0, 0);
    lcd.print("COCKTAILS");
    lcd.setCursor(0, 1);
    lcd.print("Preis :"); 
    lcd.setCursor(10, 1);
    lcd.write(byte(0));
    lcd.setCursor(12, 1);
    lcd.print(cocktailCost);
    delay(3000);
  }
  else if (money <= cocktailCost)
  {
    lcd.setCursor(0, 0);
    lcd.print("M\365nzen einwerfen");
    lcd.setCursor(0, 1);
    lcd.print("zu bezahlen "); // ich weiss natürlich nicht , ob Dein Display das €-Zeichen kennt
    lcd.print(cocktailCost - money);
  }
  if (money >= cocktailCost)
  {

    return (money >= cocktailCost);
  }
  long timeFromLastPulse = millis() - timeLastPulse;
  if (pulses > 0 && timeFromLastPulse > 200)
  {
    // sequence of pulses stopped; determine the coin type;
    if (pulses == 1)
    {
      Serial.println("Received 10 cent (1 pulse)");
      money += .1;
    }
    else if (pulses == 2)
    {
      Serial.println("Received 20 Cent (2 pulses)");
      money += .20;
    }
    else if (pulses == 5)
    {
      Serial.println("Received 50 cent (5 pulses)");
      money += 0.50;
    }
    else if (pulses == 10)
    {
      Serial.println("Received 1 Euro (10 pulses)");
      money += 1.00;
    }
    else if (pulses == 20)
    {
      Serial.println("Received 2 Euro (20 pulses)");
      money += 2.00;
    }
    else if (pulses == 25)
    {
      Serial.print("Received SpezialFree (");
      Serial.print(25);
      Serial.println(" pulses)");
      money += cocktailCost;
    }
    else
    {
      Serial.print("Unknown coin: ");
      Serial.print(pulses);
      Serial.println(" pulses");
    }
    pulses = 0;
  }
  return false;
}
//----------------------------bool MoneyIn ende--------------------
//-----------------COIN-ACCEPTOR ENDE------------------------------
  1. Teil
bool CocktailMaker(bool Service) {
  
  while (Serial2.available() > 0) {
    char ch = Serial2.read();
    serialBuffer += ch;
    if (ch == '\n') {
      for (int i = 0; i < serialBuffer.length(); i++) {
        if (serialBuffer.substring(i, i + 1) == ",") {
          actions[counter] = serialBuffer.substring(lastIndex, i);
          lastIndex = i + 1;
          counter++;
        }

        if (i == serialBuffer.length() - 1) {
          actions[counter] = serialBuffer.substring(lastIndex, i);
        }
      }

      for (int z = 0; z < TOTAL_ACTIONS; z++) {
        if (actions[z] != "0") {
          parseInput(actions[z]);
        }
      }

      Serial2.println("H");

      for (int y = 0; y < TOTAL_ACTIONS; y++) {
        actions[y] = "0";
      }
      serialBuffer = "";
      counter = 0;
      lastIndex = 0;
    }
  }
  if (CocktailFinishd == true || Service == true) return CocktailFinishd;
}
//---------------------bool CocktailMaker() ende

void parseInput(String input) {
  input.trim();
  byte command = input.charAt(0);

  switch (command) {
    case 'H':
      homeXAxis();
      break;
    case 'X':
      moveXTo(input);
      break;
    case 'F':
      pour(input);
      break;
  }
}
//------------------------parseInput() ende---------------------------------

void moveXTo(String input) {
  int pos = input.substring(1).toInt();

  Serial.print("X goes to: ");
  Serial.println(pos);

  Serial2.println(input);

  if (pos < 0 && pos >= X_MAX_POS) {
    stepper.setAcceleration(X_ACCELERATION);
    stepper.moveTo(pos);
    if (pos < stepper.currentPosition()) {
      stepper.setSpeed(-100);
    } else {
      stepper.setSpeed(100);
    }
    while (stepper.distanceToGo() != 0) {
      stepper.run();
    }
  } else {
    Serial.println("Position should be between -4995 and 0");
  }
}
//----------------------moveXTO() ende------------------------------------

void pour(String input) {
  int count = 0; // Pour counter
  int times = 0; // Times to pour
  int holdDuration = 0; // Time duration to hold dispenser in open position
  int waitDuration = 0; // Time duration to wait till next pour

  for (int z = 0; z < input.length(); z++) {
    byte parameter = input.substring(z, z + 1).charAt(0);

    switch (parameter) {
      case 'F':
        times = getParameterValue(input, z);
        break;
      case 'H':
        holdDuration = getParameterValue(input, z);
        break;
      case 'W':
        waitDuration = getParameterValue(input, z);
        break;
    }
  }

  if (holdDuration > 0 && waitDuration > 0) {
    for (int i = 0; i < times; i++) {
      maestro.setSpeed(0, SERVO_RAISE_SPEED);
      maestro.setTarget(0, SERVO_MAX_POS);
      delay(holdDuration);
      maestro.setSpeed(0, SERVO_RELEASE_SPEED);
      maestro.setTarget(0, SERVO_MIN_POS);
      if (times - 1 > count) {
        delay(waitDuration);
      } else {
        delay(DELAY_BETWEEN_INGREDIENTS);
      }
      count++;
    }
  } else {
    Serial.println("Hold and wait duration parameters cannot be lower than or equal to 0");
  }
}
//---------------------------------pour() ende------------------------------

int getParameterValue(String input, int z) {
  for (int y = z + 1; y < input.length(); y++) {
    if (input.substring(y, y + 1) == " ") {
      return input.substring(z + 1, y).toInt();
      break;
    }
    if (y == input.length() - 1) {
      return input.substring(z + 1, y + 1).toInt();
    }
  }
}
//-------------------------END--------------------------------------------------

LG Stefan

Hallo gogolog1905,
hier hab ich Dir den Sketch noch an Dein Display OHNE I2C Modul angepasst.
Wie Du das Display anschliessen musst, steht (ziemlich) am Anfang im Sketch.

Du findest den Sketch im Anhang.

gogolog1905_CoctailMaker_4-bit_LCD.ino (11.7 KB)

Ein Tip: Ich würde die Zahl der Impulse pro Münz-Art auf das absolute Minimum reduzieren. Ich kenne die Münzprüfer, die nach dem Anlernen eine bestimmte Anzahl von Impulsen ausgeben. Mit bis zu 20 Impulsen dauert das m.E. viel zu lange und der Kunde denkt, da ist was kaputt.

Verändere die Anzahl der Impulse und korrigiere das dann intern per Software, also 10ct=1imp, 20ct=2imp, 50ct=3imp, 1€=4imp, 2€=5imp.

Und ich hoffe, du hast mindestens jeweils 20 [u]verschiedene[/u] reale und unterschiedlich alte Münzen zum Anlernen des Münzprüfers verwendet, sonst kommt da noch viel Freude auf. Beim Geld hört gewöhnlich die Freundschaft auf ...

Hallo qualidat,

danke für den Tipp..... unterschiedliche gleiche Münzen habe ich verwendet, aber das mit den Impulsen werde ich wohl noch ändern.

VG Gogolog

@Gogolog Ich glaube, da gibt es auch noch nen 3-Stufigen Switch, wo man die Geschwindigkeit der Impulse ändern kann, Hab da grad vor ein paar stunden ins Manual geschaut, und es zudem auch in Video-Tutorials gesehn.

Kanst es ja zuerst da probieren und erst dann im Sketch ändern, wenns wirklich nicht anders geht. Hoffe Du hast meine neueste PN schon gesefen? LG Stefan