/* AllSkyHtr433.ino 3/03/2024  Control Allsky heater with Wx data via 433 MHz
 * 433 MHz Wx reception code based from : Ray Wang (Rayshobby LLC)
 * Uses Adafruit Feather M0 WiFi, RXB6/RXB12 433 MHz receiver, MOSFET heater driver
 * Reads Wx data from an Acurite 592TXR Temperature/Humidity "Tower" sensor
 * Default PWM output of Feather M0 WiFi is at 733 Hz
 * Creates a web server to allow retreval of data in csv format
 */

#include <SPI.h>
#include <WiFi101.h>
#include "secrets.h"    // contains WiFi network connection info

// 433 reception defines
#define RING_BUFFER_SIZE  256   // large enough to fit data between two successive syncs
#define SYNC_LENGTH 2200
#define SYNC_HIGH  600
#define SYNC_LOW   600
#define BIT1_HIGH  400
#define BIT1_LOW   220
#define BIT0_HIGH  220
#define BIT0_LOW   400

// heater function defines
#define SENSOR_ID 43          // ID of Acurite Wx sensor to monitor
#define DATAPIN 5             // Connection to output of 433 MHz receiver
#define PWM_PIN 10            // PWM output to heater circuit
#define HTR_ON_THRES 70       // turn heater on >= humidity level
#define HTR_MAX_THRES 95      // heater at 100% power >= humidity level

// 433 reception global variables
unsigned long timings[RING_BUFFER_SIZE];
unsigned int syncIndex1 = 0;  // index of the first sync signal
unsigned int syncIndex2 = 0;  // index of the second sync signal
bool received = false;

// WiFi connection global variables
char ssid[] = SECRET_SSID;        // WiFi network SSID
char pass[] = SECRET_PASS;        // WiFi network password
//IPAddress ip(HOST_IP);          // static IP address for WiFi board
IPAddress ip(192,168,1,215);      // static IP address for WiFi board
int wifiStatus = WL_IDLE_STATUS;
bool wifiPresent = false;
WiFiServer server(80);

// Wx global variables
int tempC = 0; int tempF = 0; int humidity = 0; int dewPoint = 0;   // variables from selected sensor
int tC = 0; int tF = 0; int hum = 0; int dP = 0;                    // temporary variables from 433 receiver
int pwmPercent  = 0;                                    // pwm value to drive heater power with
float pwmScale = 100 / (HTR_MAX_THRES - HTR_ON_THRES);  // adjust for threshold changes

//***** 433 MHz reception routines *****
// detect if a sync signal is present
bool isSync(unsigned int idx) {
  // check if we've received 4 squarewaves of matching timing
  int i;
  for(i=0;i<8;i+=2) {
    unsigned long t1 = timings[(idx+RING_BUFFER_SIZE-i) % RING_BUFFER_SIZE];
    unsigned long t0 = timings[(idx+RING_BUFFER_SIZE-i-1) % RING_BUFFER_SIZE];    
    if(t0<(SYNC_HIGH-100) || t0>(SYNC_HIGH+100) ||
       t1<(SYNC_LOW-100)  || t1>(SYNC_LOW+100)) {
      return false;
    }
  }
  // check if there is a long sync period prior to the 4 squarewaves
  unsigned long t = timings[(idx+RING_BUFFER_SIZE-i)%RING_BUFFER_SIZE];
  if(t<(SYNC_LENGTH-400) || t>(SYNC_LENGTH+400) ||
    digitalRead(DATAPIN) != HIGH) {
    return false;
  }
  return true;
}

/* Interrupt 1 handler */
void handler() {
  static unsigned long duration = 0;
  static unsigned long lastTime = 0;
  static unsigned int ringIndex = 0;
  static unsigned int syncCount = 0;

  // ignore if we haven't processed the previous received signal
  if (received == true) {
    return;
  }
  // calculating timing since last change
  long time = micros();
  duration = time - lastTime;
  lastTime = time;

  // store data in ring buffer
  ringIndex = (ringIndex + 1) % RING_BUFFER_SIZE;
  timings[ringIndex] = duration;

  // detect sync signal
  if (isSync(ringIndex)) {
    syncCount ++;
    // first time sync is seen, record buffer index
    if (syncCount == 1) {
      syncIndex1 = (ringIndex+1) % RING_BUFFER_SIZE;
    } 
    else if (syncCount == 2) {
      // second time sync is seen, start bit conversion
      syncCount = 0;
      syncIndex2 = (ringIndex+1) % RING_BUFFER_SIZE;
      unsigned int changeCount = (syncIndex2 < syncIndex1) ? (syncIndex2+RING_BUFFER_SIZE - syncIndex1) : (syncIndex2 - syncIndex1);
      // changeCount must be 122 -- 60 bits x 2 + 2 for sync
      if (changeCount != 122) {
        received = false;
        syncIndex1 = 0;
        syncIndex2 = 0;
      } 
      else {
        received = true;
      }
    }
  }
}
//***************************************


void setup() {
  // Configure the Feather ATWINC1500 connections
  WiFi.setPins(8,7,4,2);

  Serial.begin(9600);
  delay(1000);
  Serial.println("AllSkyHtr433 Started.");

  pinMode(DATAPIN, INPUT);    // put the 433 MHz data pin in input mode
  attachInterrupt(digitalPinToInterrupt(DATAPIN), handler, CHANGE);
  pinMode(PWM_PIN, OUTPUT);   // put the PWM pin in output mode
  pinMode(13, OUTPUT);        // put the red LED pin in output mode
  digitalWrite(13, LOW);      // turn the red LED off

  // check for presence of onboard WiFi system:
  if (WiFi.status() == WL_NO_SHIELD) {
    wifiPresent = false;
    Serial.println(">> Onboard WiFi system not detected <<");
  }
  else {      // WiFi system is present to try to connect to it
    wifiPresent = true;
    WiFi.config(ip);                      // use this static IP address instead of DNS 
    while (wifiStatus != WL_CONNECTED) {  // this should abort if tried too many times
      Serial.print("Attempting to connect to "); Serial.println(ssid);
      wifiStatus = WiFi.begin(ssid, pass);
      delay(5000);                        // wait 5 seconds to allow for connection:
    }
    server.begin();
    printWiFiStatus();                    // print out wifi status after connection
  }
}

int t2b(unsigned int t0, unsigned int t1) {
  if (t0>(BIT1_HIGH-100) && t0<(BIT1_HIGH+100) &&
      t1>(BIT1_LOW-100) && t1<(BIT1_LOW+100)) {
    return 1;
  } else if (t0>(BIT0_HIGH-100) && t0<(BIT0_HIGH+100) &&
             t1>(BIT0_LOW-100) && t1<(BIT0_LOW+100)){
    return 0;
  }
  return -1;  // undefined
}

void loop() {
  WiFiClient client = server.available(); // listen for a web client to connect
  if (client) {         // service the web client connection
    Serial.println("Web client connected");
    // an http request ends with a blank line
    bool currentLineIsBlank = true;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
        if (c == '\n' && currentLineIsBlank) {
          // send a standard http response header
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");  // the connection will be closed after completion of the response
          //client.println("Refresh: 5");  // refresh the page automatically every 5 sec
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          // output the web page content in csv format
          client.print("TempC,TempF,DewPoint,Humidity,HtrPower"); client.println("<br />");
          client.print(tempC); client.print(","); client.print(tempF); client.print(","); client.print(dewPoint); 
          client.print(","); client.print(humidity); client.print(","); client.print(pwmPercent); client.println("<br />");
          client.println("</html>");
          break;
        }
      }
    }
    delay(1);         // give the web browser time to receive the data
    client.stop();    // close the connection
    Serial.println("client disconnected");
  }  // end of web client processing

  if (received == true) {     // process the received 433 data
    // disable interrupt to avoid new data corrupting the buffer
    detachInterrupt(digitalPinToInterrupt(DATAPIN));
    
    unsigned int startIndex, stopIndex;
    
    // extract sensorID value
    unsigned int sensorID = 0;
    bool fail = false;
    bool decodeFail = false;
    startIndex = (syncIndex1 + (1*8+1)*2) % RING_BUFFER_SIZE;
    stopIndex  = (syncIndex1 + (1*8+8)*2) % RING_BUFFER_SIZE;
    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {
      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);
      sensorID = (sensorID<<1) + bit;
      if (bit < 0)  fail = true;                  // fail for this value
    }
    if (fail) {
      Serial.print("sensorID decoding error: ");
      Serial.println(sensorID);                   // print the erroneous value
      decodeFail = true;                          // fail all values for this loop
    }
    else {
      Serial.print("\nSensorID: ");
      Serial.println(sensorID);
    }

    // extract humidity value
    hum = 0;
    fail = false;
    startIndex = (syncIndex1 + (3*8+1)*2) % RING_BUFFER_SIZE;
    stopIndex =  (syncIndex1 + (3*8+8)*2) % RING_BUFFER_SIZE;
    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {
      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);
      hum = (hum<<1) + bit;
      if (bit < 0)  fail = true;                  // fail for this value
    }
    if (fail) {
      Serial.print("hum decoding error: ");
      Serial.println(hum);                        // print the erroneous value
      decodeFail = true;                          // fail all values for this loop
    }
    else {
    if (hum < 0) hum = 0;                         // ensure minimum limit
    if (hum > 100) hum = 100;                     // ensure maximum limit
    Serial.print(hum); Serial.print(",");
    }
    
    // extract temperature from two bytes
    //unsigned long temp = 0;
    int temp = 0;
    fail = false;
    // most significant 4 bits
    startIndex = (syncIndex1 + (4*8+4)*2) % RING_BUFFER_SIZE;
    stopIndex  = (syncIndex1 + (4*8+8)*2) % RING_BUFFER_SIZE;
    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {
      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);
      temp = (temp<<1) + bit;
      if (bit < 0)  fail = true;                  // fail for this value     
    }
    // least significant 7 bits
    startIndex = (syncIndex1 + (5*8+1)*2) % RING_BUFFER_SIZE;
    stopIndex  = (syncIndex1 + (5*8+8)*2) % RING_BUFFER_SIZE;
    for(int i=startIndex; i!=stopIndex; i=(i+2)%RING_BUFFER_SIZE) {
      int bit = t2b(timings[i], timings[(i+1)%RING_BUFFER_SIZE]);
      temp = (temp<<1) + bit;
      if (bit < 0)  fail = true;                  // fail for this value      
    }
    if (fail) {
      Serial.print("temp decoding error: ");
      Serial.println(temp);                       // print the erroneous value
      decodeFail = true;                          // fail all values for this loop
    }
    else {
      tC = int((temp-1024)/10+1.9+0.5);           // round to the nearest integer
      tF = int(tC*9/5+32+0.5);                    // round to the nearest integer
      if (!decodeFail) dP = int((tC-(100-hum)/5)*9/5+32+0.5);    // simplistic approximation calculation   
      Serial.print(tC); Serial.print(","); Serial.print(tF); Serial.print(","); Serial.println(dP);
    } 
    // delay for 1 second to avoid repetitions
    delay(1000);
    received = false;
    syncIndex1 = 0;
    syncIndex2 = 0;

    // Set Wx values for web page and output PWM signal to Heater
    if (sensorID == SENSOR_ID && !decodeFail) {                   // only use selected sensor
      tempC = tC; tempF = tF; dewPoint = dP; humidity = hum;      // save values from selected sensor
      pwmPercent = int((humidity - HTR_ON_THRES) * pwmScale);     // using measured humidity
      //pwmPercent = int(random(0,100));                          // using random humidity for testing
      if (pwmPercent < 0) pwmPercent = 0;                         // ensure lower limit
      if (pwmPercent > HTR_MAX_THRES) pwmPercent = 100;           // ensure upper limit
      Serial.print("Set PWM to "); Serial.print(pwmPercent); Serial.print(" on pin "); Serial.println(PWM_PIN);
      analogWrite(PWM_PIN, int(pwmPercent*2.55));                 // scale to 0-255 range needed by analogWrite
    }

    blink13(25);  // blink the red LED when 433 MHz data is received

    // re-enable interrupt
    attachInterrupt(digitalPinToInterrupt(DATAPIN), handler, CHANGE);
  }

}

void printWiFiStatus() {
  // print the SSID of the network the board is attached to:
  Serial.print("Connected to SSID: "); Serial.println(WiFi.SSID());

  // print the onboard WiFi system IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("Board's IP Address: "); Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):"); Serial.print(rssi); Serial.println(" dBm");
}

void blink13(int mS){
  digitalWrite(13, HIGH);     // turn LED on
  delay(mS);                  // wait time in mSec
  digitalWrite(13, LOW);      // turn LED off
}