/*******************************************************************************
 * Program...: deskcontrol.ino Monitor environment in new deskcontrol
 * Author....: Jan-marie Newton
 * Date......: 2024-04-09
 * History...: 
 * Notes.....: version 9.8
 *******************************************************************************/
#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h> // UDP library from: bjoern@cs.stanford.edu 12/30/2008
#include <DHT_Async.h>
#include "MQ7.h"


// Constants - set relay pin numbers:
//------------------------------------------------------------------------------
#define UP_RELAY   4
#define DN_RELAY   5
#define HALO_RELAY 6
#define TRAC_RELAY 7  
#define CO_GAS    A2
#define DHT_PIN    9   //  tempurature & humidity
#define TOP       18
#define MID       19
#define BOT       20
#define VOLTAGE    5


// DHT_SENSOR_TYPEs: DHT_TYPE_11 | DHT_TYPE_21 | DHT_TYPE_22
DHT_Async dht(DHT_PIN, DHT_TYPE_11);

// CO sensor
MQ7 mq7(CO_GAS, VOLTAGE);

// Initialize Variables
//------------------------------------------------------------------------------
EthernetUDP Udp;         // An EthernetUDP instance to let us send and receive packets over UDP
IPAddress ip(192,168,1,115);
byte mac[]             = {0xDE, 0xAD, 0xBE, 0xEE, 0xEE, 0xED}; // Enter a MAC address and IP address
unsigned int localPort = 8888;      // local port to listen on
EthernetClient client;
IPAddress host(192, 168, 1, 106);
unsigned int hostPort  = 48388;     // host port to push to

// buffers for receiving and sending data
char packetBuffer[UDP_TX_PACKET_MAX_SIZE];      // buffer to hold incoming packet
char replyBuffer[]     = "747.049.1.182.000";   // a string to send back
const char* cmds[]     = {"flip", "halo", "off", "on", "top", "bott", "stop","mid", "up", "down"};
volatile byte top_stat;
volatile byte bot_stat;
volatile byte mid_stat;

//----------------------------
bool debug    = false;
int direction = 0;
int travel    = 0;
bool midstop  = false;
float ih      = 50;
float it      = 40;
float io      = 0;   // box temperature measurement error offset
float co      = 0;
int dlay      = 0;
char posOrg   = 'W';
char pos      = 'W'; // 0-4 = position of curtain  U=unknown, W=waiting for response
String rec;
unsigned long status_timestamp = 0;
unsigned long debounce         = 0;
unsigned long deb_delay        = 90;


//------------------------------------------------------------------------------
void stopRun(bool update=false) {
   digitalWrite(DN_RELAY, LOW);
   digitalWrite(UP_RELAY, LOW);
   direction = 0;
   midstop   = false;
   if (update) {
      top_stat = digitalRead(TOP);  // 0 = engaged  1 = open
      mid_stat = digitalRead(MID);  // 0 = engaged  1 = open
      bot_stat = digitalRead(BOT);  // 0 = engaged  1 = open
   }
   getCurrentPos();
}
//------------------------------------------------------------------------------

// Status update
//----------------------------------------------------------------------------------
void getStatus() {
   //getCurrentPos();

   if (debug) {
      Serial.print(status_timestamp);
      Serial.print("  InSide: ");
      Serial.print(it,2);     // inside temperature
      Serial.print(" humidity: ");
      Serial.print(ih,1);     // inside humidity
      Serial.print(" position: ");
      Serial.print(pos); // curtain position
      Serial.print(" CO Level: ");
      Serial.print(co,2);     // Carbon Monoxide level
      Serial.print(" light: ");
      Serial.print(digitalRead(TRAC_RELAY));     // relay for desk lights
      Serial.println(digitalRead(HALO_RELAY));   // relay for center light
   }

   rec  = String(round(it * 10));      // inside temperature to 1/10 of degree
   rec += "\t"; rec += round(ih);      // inside humidity
   rec += "\t"; rec += pos;            // position of curtain
   rec += "\t"; rec += round(co * 10); // Carbon Monoxide level
   rec += "\t"; rec += digitalRead(TRAC_RELAY); rec += digitalRead(HALO_RELAY);
   rec.toCharArray(replyBuffer, rec.length() + 1);
   status_timestamp = millis();
   rec = "  Tx\t" + rec;
}
//----------------------------------------------------------------------------------

//----------------------------------------------------------------------------------
void getCurrentPos() {
   if (top_stat == LOW)
      pos = '4';
   else if (bot_stat == LOW)
      pos = '0';
   else if (mid_stat == LOW) {
      pos = '2';
      travel = direction;
   } else if (travel == 1)
      pos = '3';
   else
      pos = '1';
}
//----------------------------------------------------------------------------------

//----------------------------------------------------------------------------------
static bool measure_environment() {
   static unsigned long dht_timestamp = millis();

   /* Measure once every four seconds. */
   if (millis() - dht_timestamp > 9900ul) {
       co = mq7.readPpm();
       if (dht.measure(&it, &ih)) {
          dht_timestamp = millis();
          it = dht.convertCtoF(it + io);
          return true;
       }
   }
   return false;
}
//----------------------------------------------------------------------------------


// Setup Initialization
//----------------------------------------------------------------------------------
void setup() {
   // start the Ethernet and UDP:
   Ethernet.begin(mac, ip);
   Udp.begin(localPort);
   for(int i=0; i<4; i++)
      packetBuffer[2+i] = 0;

   // initialize the relay pins as an output:
   pinMode(HALO_RELAY, OUTPUT);
   pinMode(TRAC_RELAY, OUTPUT);
   pinMode(UP_RELAY, OUTPUT);
   pinMode(DN_RELAY, OUTPUT);
   pinMode(TOP, INPUT_PULLUP);
   pinMode(MID, INPUT_PULLUP);
   pinMode(BOT, INPUT_PULLUP);
   pinMode(CO_GAS,     INPUT);
   digitalWrite(HALO_RELAY, LOW);
   digitalWrite(TRAC_RELAY, LOW);
   stopRun(true);

   // find current pos if curtain
   measure_environment();
   status_timestamp = millis();

   if (debug) {
      Serial.begin(115200);
      Serial.println("Calibrating MQ7");
   }
   mq7.calibrate();    // calculates R0
   if (debug)
      Serial.println("Calibration done!");

   attachInterrupt(digitalPinToInterrupt(TOP), limits, CHANGE);
   attachInterrupt(digitalPinToInterrupt(MID), limits, CHANGE);
   attachInterrupt(digitalPinToInterrupt(BOT), limits, CHANGE);
   measure_environment();
   getCurrentPos();
}
//----------------------------------------------------------------------------------

// Main event loop
//----------------------------------------------------------------------------------
void loop() {
   // reset debounce
   if (debounce and ((millis() - debounce) >= deb_delay))
      debounce = 0;

   // Measure temperature and humidity and CO levels.
   measure_environment();
   if ((millis() - status_timestamp) >= 15000ul)
      getStatus();

   // update host device controler
   if (pos != posOrg) {
      posOrg = pos;
      pushStatus();
   }

   // if there's data available, read a packet
   int packetSize = Udp.parsePacket();
   if (packetSize) {
      Udp.read(packetBuffer, UDP_TX_PACKET_MAX_SIZE); // read the packet into packetBuffer
         /*Serial.print("Received packet of size ");
         Serial.println(packetSize);
         Serial.print("From ");
         IPAddress remote = Udp.remoteIP();
         for (int i = 0; i < 4; i++) {
              Serial.print(remote[i], DEC);
              if (i < 3) Serial.print(".");
          }
          Serial.print(", port ");
          Serial.println(Udp.remotePort());
          Serial.print("Contents: ");
          Serial.println(packetBuffer);*/
      if (packetBuffer[0] != 'r') {
         for (int i=0; i < 10; i++) {
            if (strcmp(packetBuffer, cmds[i]) == 0) {
               obeyCommmad(cmds[i]);
               break;
            }
         }
      }
      // send a reply to the IP address and port that sent us the packet we received
      getCurrentPos();
      getStatus();
      if (debug) {
         Serial.print(packetBuffer);
         Serial.print(" -> ");
         Serial.println(replyBuffer);
      }
      Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
      Udp.write(replyBuffer);
      Udp.endPacket();
      //
      for (int i=0; i<4; i++)
         packetBuffer[2+i] = 0;

      if (dlay) {
         delay(dlay);
         stopRun();
         dlay = 0;
      }
   }
}
//----------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void limits() {
   if (debounce) return;
   debounce = millis();
   top_stat = digitalRead(TOP);  // 0 = engaged  1 = open
   mid_stat = digitalRead(MID);  // 0 = engaged  1 = open
   bot_stat = digitalRead(BOT);  // 0 = engaged  1 = open
   if (mid_stat == LOW)
      travel = direction;

   if (midstop and mid_stat == LOW)
      stopRun();
   else if (direction == 1 and top_stat == LOW)
      stopRun();
   else if (direction == -1 and bot_stat == LOW)
      stopRun();
   else
      getCurrentPos();
}
//------------------------------------------------------------------------------

// Process incoming command
//cmds = {"flip", "halo", "off", "on", "top", "bott", "stop","mid", "up", "down"}
//----------------------------------------------------------------------------------
void obeyCommmad(const char* cmd) {
   if (debug) {
      Serial.print("set command: ");
      Serial.println(cmd);
   }

   if (cmd == "halo")
      digitalWrite(HALO_RELAY, 1 - digitalRead(HALO_RELAY));   // flip center light 3-way switch

   else if (cmd == "flip")
      digitalWrite(TRAC_RELAY, 1 - digitalRead(TRAC_RELAY));   // flip circle track light 3-way switch

   else if (cmd == "on") {
      digitalWrite(HALO_RELAY, HIGH);
      digitalWrite(TRAC_RELAY, HIGH);
   }
   else if (cmd == "off") {
      digitalWrite(HALO_RELAY, LOW);
      digitalWrite(TRAC_RELAY, LOW);
   }
   else if (cmd == "top")
      goUp();
   else if (cmd == "bott")
      goDown();
   else if (cmd == "mid") {
      midstop = true;
      goUp();
   }
   else if (cmd == "stop")
      stopRun();
   else if (cmd == "up") {
      goUp();  dlay = 50;
   }
   else if (cmd == "down") {
      goDown();  dlay = 50;
   }
}
//----------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void goUp() {
   top_stat = digitalRead(TOP);  // 0 = engaged  1 = open
   if (top_stat == LOW) {
      if (debug) Serial.println(" Already on Top");
      if (midstop and mid_stat == HIGH)
         goDown();
      else
         stopRun();

      return;  // already on top
   }

   /* sometimes the motor start-up will cause an EM spike, that triggers the 
    *  top magnetic limit switch, stopping the rise of the curtain even
    *  as it starts.  Setting the debounce to > 0 for just 10 milliseconds
    *  will allow the program to ignore it.
    */
   debounce  = 150;  // ignore 
   direction = 1;
   digitalWrite(DN_RELAY, LOW);
   digitalWrite(UP_RELAY, HIGH);
   delay(10);
   debounce = 0;
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void goDown() {
   bot_stat = digitalRead(BOT);  // 0 = engaged  1 = open
   if (bot_stat == LOW) {
      if (debug) Serial.println(" Already on bottom");
      if (midstop and mid_stat == HIGH)
         goUp();
      else
         stopRun();

      return;  // already on bottom
   }

   /* we dont have to create any delay here because we are already ignoring
    *  the top switch.
    */
   direction = -1;
   digitalWrite(DN_RELAY, HIGH);
   digitalWrite(UP_RELAY, LOW);
}
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
void pushStatus() {
   unsigned long tmp = debounce;
   if (client.connect(host, hostPort)) {
      debounce = millis();   // prevent interupt from firing
      getStatus();
      rec[0] = 0;
      rec[1] = rec.length() - 2;
      debounce = millis();   // prevent interupt from firing
      client.print(rec);
      client.stop();

   } else if (debug)
      Serial.println("UNABLE to connect to Host");

   debounce = tmp;
}
//------------------------------------------------------------------------------
