/*
* CENTRAL UNIT (Master) - Poffertjes Machine Controller
* Master  gives instructions to TRANSPORT, DISPENSER, HEATER and UX
* Uses text-based commands over I2C for better readability
* 
* Other components will be
* - TRANSPORT (slave) has bus address 0x08
* - BATTER has bus address 0x09
* - STARDUST (slave) has bus address 0x0A
* - HEATER (slave)has bus address 0x10
*
* Idea is that the master sends full text command (e.g. "POSITION-PAN1", "DISPENSE", etc.)
* Slave returns only 1 byte (simple status code)
* For readability, master converts the byte into readable text for Serial printing and debugging
*/

#include <Arduino.h>
#include <Wire.h> // need this for I2C communication

// I2C ADDRESSES
#define TRANSPORT_ADDR   0x08
#define BATTER_ADDR      0x09
#define STARDUST_ADDR    0x0A
#define HEATER_ADDR      0x10

int glb_baking_time = 240000; // 4 minutes = 4 x 60 x 1000 miliseconds

// TEXT COMMANDS
const char* CMD_STATUS              = "STATUS";

// TRANSPORT commands
const char* CMD_POSITION_PAN1       = "POSITION-PAN1";
const char* CMD_POSITION_PAN2       = "POSITION-PAN2";
const char* CMD_POSITION_IN_HEATER  = "POSITION-PANS-IN-HEATER";
const char* CMD_POSITION_UNDER_DUST = "POSITION-PANS-UNDER-DISPENSER";
const char* CMD_POSITION_SERVE      = "POSITION-SERVE";

// HEATER commands
const char* CMD_OPEN_LID            = "OPEN-LID";
const char* CMD_CLOSE_LID           = "CLOSE-LID";

// BATTER commands
const char* CMD_DISPENSE_BATTER     = "DISPENSE";

// STARDUST commands
const char* CMD_DISPENSE_STARDUST   = "DISPENSE-STARDUST";

// UX commands
const char* CMD_CONFIRM_START       = "CONFIRM-START";
const char* CMD_CONFIRM_BAKED       = "CONFIRM-POFS-BAKED";
const char* CMD_CONFIRM_DUSTED      = "CONFIRM-POFS-DUSTED";


// Response codes that can later be linked to text
#define TRANSPORT_READY     10
#define HEATER_READY        11
#define BATTER_READY        12
#define STARDUST_READY      13
#define START_CONFIRMED     14

#define PAN1_READY          20
#define BATTER_DISPENSED    21
#define PAN2_READY          22

#define LID_OPEN            30
#define PANS_IN_HEATER      31
#define LID_CLOSED          32

#define POFS_BAKED          40
#define PANS_UNDER_DUST     41
#define STARDUST_DISPENSED  42
#define POFS_DUSTED         43

// Helper function to convert the response code from a slave back to readable text
const char* getResponseText(byte code) {
  switch (code) {
    case TRANSPORT_READY:    return "TRANSPORT-READY";
    case HEATER_READY:       return "HEATER-READY";
    case BATTER_READY:       return "BATTER-READY";
    case STARDUST_READY:     return "STARDUST-READY";
    case START_CONFIRMED:    return "START-CONFIRMED";

    case PAN1_READY:         return "PAN1-READY";
    case BATTER_DISPENSED:   return "BATTER-DISPENSED";
    case PAN2_READY:         return "PAN2-READY";

    case LID_OPEN:           return "LID-OPEN";
    case PANS_IN_HEATER:     return "PANS-IN-HEATER";
    case LID_CLOSED:         return "LID-CLOSED";

    case POFS_BAKED:         return "POFS-BAKED";
    case PANS_UNDER_DUST:    return "PANS-UNDER-DISPENSER";
    case STARDUST_DISPENSED: return "STARDUST-DISPENSED";
    case POFS_DUSTED:        return "POFS-DUSTED";

    default:                 return "UNKNOWN";
  }
}

void sendMsg2Slave(byte addr, const char* cmd) {

  // log to the computer 
  Serial.print("Sending to Slave: ");
  Serial.print(addr);
  Serial.print(" / command: ");
  Serial.println(cmd);

  // do the actual sending of the message
  Wire.beginTransmission(addr);
  Wire.write(cmd); // Send command/data
  Wire.endTransmission(); // could be improved with error handling!!

  delay(10); // Small stability delay
}

/* now this function needs to return something */
byte requestAnswer(byte addr) {

  Wire.requestFrom(addr, (byte)1); 
  if (Wire.available()) {
    byte response = Wire.read();

    Serial.print("Update from "); 
    Serial.print(addr);
    Serial.print(": "); 
    Serial.print(response);
    Serial.print(" (converted): ");
    Serial.println(getResponseText(response));
    return response;
  }
  return 0;
}

// New function that sends a command and polls for the answer
bool sendCommandAndWait(byte addr, const char* cmd) {

  sendMsg2Slave(addr, cmd);

  // please https://docs.arduino.cc/language-reference/en/functions/time/millis/ for more info on millis
  // 'Returns the number of milliseconds passed since the Arduino board began running the current program' 
  unsigned long start = millis();
  while (millis() - start < 8000) {
    byte resp = requestAnswer(addr);
    Serial.print("Reponse = ");
    Serial.println(resp);
    if (resp > 0) {
      return true; // anything above 0 is ok
    }
    delay(150);                 // still busy, keep polling
  }

  // so no answer within 8 seconds ... then there must be something wrong

  Serial.print("Timeout waiting for response from: ");
  Serial.println(addr);
  return false;
}


void setup() {

  Serial.begin(115200);  // USB Serial for debugging
  Wire.begin(); // Act as Master
  Serial.println("Setup as master");
  delay(1000); // just wait a second
}

void loop() {
 
  Serial.println("Starting baking cycle");

  // Check that all components are ready
  if (!sendCommandAndWait(TRANSPORT_ADDR, CMD_STATUS)) goto abort;
//  if (!sendCommandAndWait(HEATER_ADDR,    CMD_STATUS)) goto abort;
//  if (!sendCommandAndWait(BATTER_ADDR,    CMD_STATUS)) goto abort;
//  if (!sendCommandAndWait(STARDUST_ADDR,  CMD_STATUS)) goto abort;

  // if (!sendCommandAndWait(UX_ADDR,        CMD_CONFIRM_START)) goto abort;

  Serial.println("All systems ready + user confirmed start.");

  // Batter dispensing phase
//  if (!sendCommandAndWait(TRANSPORT_ADDR, CMD_POSITION_PAN1)) goto abort;
//  if (!sendCommandAndWait(BATTER_ADDR,    CMD_DISPENSE_BATTER)) goto abort;

  delay(1000);

//  if (!sendCommandAndWait(TRANSPORT_ADDR, CMD_POSITION_PAN2)) goto abort;
//  if (!sendCommandAndWait(BATTER_ADDR,    CMD_DISPENSE_BATTER)) goto abort;

  // Heating time
//  if (!sendCommandAndWait(HEATER_ADDR,    CMD_OPEN_LID)) goto abort;
//  if (!sendCommandAndWait(TRANSPORT_ADDR, CMD_POSITION_IN_HEATER)) goto abort;
//  if (!sendCommandAndWait(HEATER_ADDR,    CMD_CLOSE_LID)) goto abort;

  Serial.println("Baking in progress... (waiting bake time)");

 // delay(glb_baking_time);

//  if (!sendCommandAndWait(HEATER_ADDR,    CMD_OPEN_LID)) goto abort;

  // if (!sendCommandAndWait(UX_ADDR,        CMD_CONFIRM_BAKED)) goto abort;

  // Stardust time
//  if (!sendCommandAndWait(TRANSPORT_ADDR, CMD_POSITION_UNDER_DUST)) goto abort;
//  if (!sendCommandAndWait(STARDUST_ADDR,  CMD_DISPENSE_STARDUST)) goto abort;

  // if (!sendCommandAndWait(UX_ADDR,        CMD_CONFIRM_DUSTED)) goto abort;

  // Finalize cycle
//  if (!sendCommandAndWait(HEATER_ADDR,    CMD_CLOSE_LID)) goto abort;
//  if (!sendCommandAndWait(TRANSPORT_ADDR, CMD_POSITION_SERVE)) goto abort;

  Serial.println("Poffertjes are ready to eat");

  delay(3000);   // Short pause before allowing next cycle
  return;

abort:
  Serial.println("Aborted because of time-out");
  delay(5000);
}



