/*
  main_pilot_wire_control.ino
  Switches on or off several heaters.
*/

#include <Ticker.h>

#include "EspMQTTClient.h"

#define LWD_TIMEOUT_MS  15000

EspMQTTClient client(
  "rico2",
  "xxxxx",
  "192.168.1.2",  // MQTT Broker server ip
  "",   // Can be omitted if not needed
  "",   // Can be omitted if not needed
  "pilote-general",     // Client name that uniquely identify your device
  1883              // The MQTT port, default to 1883. this line can be omitted
);

enum rooms {
  LIVING_ROOM,
  ROOM_SO,
  ROOM_RICO,
  BATHROOM,
  NB_HEATERS
};

struct heater {
  int pin;
  enum rooms room;
  const char *topic;
  MessageReceivedCallback callback;
  os_timer_t keep_alive_timer;
};

static struct heater heaters[NB_HEATERS];

const int led_pin = 2;

#define led_on(state) do {\
  digitalWrite(led_pin, (state) ? LOW : HIGH); \
} while (0)

#define heater_on(pin) do {\
  digitalWrite(pin, LOW); \
} while(0)

#define heater_off(pin) do {\
  digitalWrite(pin, HIGH); \
} while(0)

// loop watchdog variables
unsigned long lwdTime = 0;
unsigned long lwdTimeout = LWD_TIMEOUT_MS;
Ticker lwdTicker;

void reset_timer(enum rooms room)
{
  os_timer_t *timer = &(heaters[room].keep_alive_timer);
  
  os_timer_disarm(timer);
  os_timer_setfn(timer, shutdown_heater, &(heaters[room].room));
  os_timer_arm(timer, 3600000, false);
}

void ICACHE_RAM_ATTR lwdtcb(void)
{
  if (((millis() - lwdTime) > LWD_TIMEOUT_MS) || ((lwdTimeout - lwdTime) != LWD_TIMEOUT_MS))
  {
    ESP.restart();
  }
}

void lwdtFeed(void) {
  lwdTime = millis();
  lwdTimeout = lwdTime + LWD_TIMEOUT_MS;
}

void handle_message(enum rooms room, const String &message)
{
  Serial.println(message);
  if (message.equals("0") || message.equalsIgnoreCase("off") || message.equalsIgnoreCase("false")) {
      heater_off(heaters[room].pin);
  }
  if (message.equals("1") || message.equalsIgnoreCase("on") || message.equalsIgnoreCase("true")) {
      heater_on(heaters[room].pin);
  }
  reset_timer(room);
}

void living_room_callback(const String &message)
{
  handle_message(LIVING_ROOM, message);
}

void room_so_callback(const String &message)
{
  handle_message(ROOM_SO, message);
}

void room_rico_callback(const String &message)
{
  handle_message(ROOM_RICO, message);
}

void bathroom_callback(const String &message)
{
  handle_message(BATHROOM, message);
}

void shutdown_heater(void *arg)
{
  enum rooms room = *(enum rooms *)arg;
  
  // if we didn't receive a message for an hour,
  // shutdown the heater
  Serial.print("No MQTT message for one hour on topic ");
  Serial.print(heaters[room].topic);
  Serial.println(", switching off.");
  heater_off(heaters[room].pin);
}

void setup()
{
  unsigned i;

  heaters[LIVING_ROOM].room = LIVING_ROOM;
  heaters[ROOM_SO].room = ROOM_SO;
  heaters[ROOM_RICO].room = ROOM_RICO;
  heaters[BATHROOM].room = BATHROOM;
  
  heaters[LIVING_ROOM].pin = 4;
  heaters[ROOM_SO].pin = 14;
  heaters[ROOM_RICO].pin = 12;
  heaters[BATHROOM].pin = 13;

  heaters[LIVING_ROOM].topic = "chauffage/salon";
  heaters[ROOM_SO].topic = "chauffage/chambre_rico";
  heaters[ROOM_RICO].topic = "chauffage/chambre_so";
  heaters[BATHROOM].topic = "chauffage/sdb";
  
  heaters[LIVING_ROOM].callback = living_room_callback;
  heaters[ROOM_SO].callback = room_so_callback;
  heaters[ROOM_RICO].callback = room_rico_callback;
  heaters[BATHROOM].callback = bathroom_callback;

  lwdTicker.attach_ms(LWD_TIMEOUT_MS / 2, lwdtcb);

  for (i = 0; i < NB_HEATERS; i++) {
    pinMode(heaters[i].pin, OUTPUT);
    heater_off(heaters[i].pin);
  }

  pinMode(led_pin, OUTPUT);

  Serial.begin(115200);

  // Optionnal functionnalities of EspMQTTClient : 
  client.enableDebuggingMessages(); // Enable debugging messages sent to serial output
  client.enableHTTPWebUpdater(); // Enable the web updater. User and password default to values of MQTTUsername and MQTTPassword. These can be overrited with enableHTTPWebUpdater("user", "password").
  for (i = 0; i < NB_HEATERS; i++) {
    os_timer_setfn(&(heaters[i].keep_alive_timer), shutdown_heater, &(heaters[i].room));
    os_timer_arm(&(heaters[i].keep_alive_timer), 3600000, false);
  }
}

// This function is called once everything is connected (Wifi and MQTT)
// WARNING : YOU MUST IMPLEMENT IT IF YOU USE EspMQTTClient
void onConnectionEstablished()
{
  for (unsigned i = 0; i < NB_HEATERS; i++) {
    client.subscribe(heaters[i].topic, heaters[i].callback);
  }
}

void loop()
{
  static unsigned int counter;
  static bool led_state = false;

  lwdtFeed();

  client.loop();
  if (!client.isConnected()) {
    // if something wrong happens, switch off the heater
    for (unsigned i = 0; i < NB_HEATERS; i++) {
      heater_off(heaters[i].pin);
    }
  }

  // blink ESP blue led to show that we are alive
  if (((led_state) && (counter % 1000 == 0)) || (!led_state && (counter % 30000 == 0))) {
    led_state = !led_state;
    led_on(led_state);
  }
  counter++;
}
