/*
 * Dampp-Chaser Monitor
 * Author: Alexander Peppe, Alex's Piano Service
 * Date: 1/21/2022
 *
 * This is based on the Adafruit FONA GPS tracker
 *
 * To use it, you'll need to add your own URL to the HTTP request.
 * You'll also need to add your own APN.
 *
 * Copyright (C) 2022 Alexander Peppe
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include <BotleticsSIM7000.h>
#include <math.h>

#include "Adafruit_MQTT.h"
#include "Adafruit_MQTT_FONA.h"

#include <DHT_U.h>
#include <DHT.h>

#define DHTPIN 3
#define DHTTYPE DHT22

/* Define pins for the Water Level sensor */
#define WL_POWER_PIN  2
#define WL_SIGNAL_PIN A0
int wl_value = 0;

DHT dht(DHTPIN, DHTTYPE);

/* I jumped pin 8 to the reset pin */
#define RESET_PIN 8

#define SIMCOM_7000
#define PROTOCOL_HTTP_GET

#define FONA_PWRKEY 6
#define FONA_RST 7
#define FONA_TX 10 // Microcontroller RX
#define FONA_RX 11 // Microcontroller TX
#define LED 13 // Just for testing if needed!

#include <SoftwareSerial.h>
SoftwareSerial fonaSS = SoftwareSerial(FONA_TX, FONA_RX);
SoftwareSerial *fonaSerial = &fonaSS;

#include <avr/sleep.h>
#include <avr/power.h>

#include <Wire.h>
#include "Adafruit_MCP9808.h"

Adafruit_MCP9808 tempsensor = Adafruit_MCP9808();
Adafruit_FONA_LTE fona = Adafruit_FONA_LTE();

/* Set the samplingRate! 
 *  
 *  A Dampp-Chaser humidity control system is NOT a rapidly changing environment,
 *  so I made the samplingRate pretty sparse. I decided on two minutes—more frequent
 *  than necessary. During testing, it's helpful to set this to a very low number.
 *  
 *  There's a twist:
 *  
 *  If you do the above multiplication in the scope of a delay by saying something like
 *        delay(samplingRate * 1000)
 *  You'll end up passing it as an integer and unpleasant side effects might occur. It's
 *  necessary to formally declare it an unsigned long.
 *  
 *  The explicit UL for unsigned long is mandatory.
 */
#define samplingRate 120
unsigned long samplingRateMils = samplingRate * 1000UL;


/* Global variables */

uint8_t readline(char *buff, uint8_t maxbuff, uint16_t timeout = 0);

char imei[16] = {0}; // Use this for device ID
uint8_t type;
uint16_t battLevel = 0; // Battery level (percentage)
uint8_t counter = 0;
char uploadCount = 0;

char URL[200];  // Make sure this is long enough for your request URL
char body[100]; // Make sure this is long enough for POST body

char netFails = 0;
char uploadFails = 0;
char skippedPosts = 0;

/* The RESET_PIN is jumped directly to the reset input on the board.
 *  There are a number of circumstances where the device will reboot
 *  itself if things don't seem to be working as planned.
 *
 *  I find it helpful to turn off the shield right before resetting
 *  the actual Arduino. Sometimes, during connection failures, it
 *  seems like the issue is something on the shield outside of this
 *  source code.
 */
void resetFunc() {
    fona.powerDown();
    digitalWrite(RESET_PIN, LOW);
}

void setup() {
    digitalWrite(RESET_PIN, HIGH);
    pinMode(RESET_PIN, OUTPUT);

    Serial.begin(9600);
    Serial.println(F("*** DC Tracker v0.3 ***"));

    pinMode(LED, OUTPUT);
    digitalWrite(LED, LOW);

    pinMode(FONA_RST, OUTPUT);
    digitalWrite(FONA_RST, HIGH); // Default state

    fona.powerOn(FONA_PWRKEY); // Power on the module
    moduleSetup(); // Establishes first-time serial comm and prints IMEI

    // tempsensor.wake(); // Wake up the MCP9808 if it was sleeping
    if (!tempsensor.begin(0x18)) {
        Serial.println(F("Couldn't find the MCP9808!"));
        resetFunc();
    }

    // Set modem to full functionality
    fona.setFunctionality(1); // AT+CFUN=1
    fona.setNetworkSettings([REDACTED]);

    /* The GPRS connection is turned off and then back on again every loop.
     *  This data connection isn't very reliable, and resetting it regularly
     *  helps to make sure we don't have failures when we post data.
     */

    if (!fona.enableGPRS(false)) Serial.println(F("Failed to disable GPRS!"));

    netFails = 0;
    while (!fona.enableGPRS(true)) {
        Serial.println(F("Failed to enable GPRS, retrying..."));
        delay(2000); // Retry every 2s
        if (netFails++ > 30) {
            Serial.print(F("Bailing."));
            resetFunc();
        }
    }
    netFails = 0;

    pinMode(WL_POWER_PIN, OUTPUT);   // configure D7 pin as an OUTPUT
    digitalWrite(WL_POWER_PIN, LOW); // turn the sensor OFF
    dht.begin(); // initialize the sensor
}

void loop() {

    /* Attempt repeatedly to connect to network and, in the event of repeated failure,
     *  reboots the device. First it will attempt to reconnect to the cellphone network,
     *  and next it will attempt to restart the data connection. 
     */
    while (!netStatus()) {
        Serial.print(F("Failed to connect to cell network, retrying for the "));
        netFails++;
        Serial.print((int)netFails);
        Serial.println(F(" time..."));
        delay(2000); // Retry every 2s

        if (netFails > 30) {
            Serial.print(F("Bailing."));
            resetFunc();
        }
    }
    netFails = 0;
    if (!fona.enableGPRS(false)) Serial.println(F("Failed to disable GPRS!"));
    while (!fona.enableGPRS(true)) {
        Serial.println(F("Failed to enable GPRS, retrying..."));
        delay(2000); // Retry every 2s
        if (netFails++ > 30) {
            Serial.print(F("Bailing."));
            resetFunc();
        }
    }
    netFails = 0;

    Serial.println(F("Connected to cell network!"));

    battLevel = readVcc(); // Get voltage in mV

    delay(500); // I found that this helps

    /* Check the water level */
    digitalWrite(WL_POWER_PIN, HIGH);     // turn the sensor ON
    delay(10);                            // wait 10 milliseconds
    wl_value = analogRead(WL_SIGNAL_PIN); // read the analog value from sensor
    digitalWrite(WL_POWER_PIN, LOW);      // turn the sensor OFF

    /* Check the temperature and humidity */
    float humi  = dht.readHumidity();
    float tempF = dht.readTemperature(true);

    /* Check if the humidity or temperature sensor failed */
    if (isnan(humi) || isnan(tempF)) {
        Serial.println("Failed to read from DHT sensor!");
    } else {
        Serial.print(F("Water Level: "));
        Serial.print(wl_value);
        Serial.print(F("   |   Humidity: "));
        Serial.print(humi);
        Serial.print(F("%"));
        Serial.print(F("   |   Temperature: "));
        Serial.print(tempF);
        Serial.println("°F");
    }

    /* Make round numbers. The humidity and temperature sensors available for Arduino boards are
     *  not spectacularly accurate, and so higher precision than "big round numbers" just isn't
     *  useful. I mostly added this out of curiosity anyway. 
     */
    int roundHum = round(humi);
    int roundTemp = round(tempF);

    sprintf(URL, "http://dweet.io/dweet/for/[REDACTED]?temp=%d&hum=%d&wl=%d", roundTemp, roundHum, wl_value);

    /* Attempt to upload data and bail if there's repeated failure */
    while (!fona.postData("GET", URL)) {

        counter++; // Increment counter

    /* This little routine is designed to work around a strange failure.
         * Periodically, I'll get an HTTP 601 or 603 error. It might have
         * something to do with rate limiting, but I wasn't able to get a
         * solution from either Telnyx or Dweet.io. The solution for me was
         * to power down the phone shield for some period (10 minutes seems
         * safe). This resets something either at Telnyx or at Dweet.io.
         *
         * I'd love input from somebody, but this solution does work.
         *
         * Sometimes, it fails for other reasons, so I give it three attempts
         * at regular samplingRate intervals.
         */
        if (counter >= 3) {
            Serial.println(F("Resetting due to too many failures."));
        Serial.println(F("Powering down Botletics shield.");
            fona.powerDown(); // Power off the module
        Serial.println(F("Waiting ten minutes..."));
            delay(600000UL); 
            
            resetFunc();
        }

        Serial.println(F("Failed to post data, retrying..."));
        delay(samplingRateMils);
    }

    Serial.print(F("Waiting "));
    Serial.print(samplingRate);
    Serial.print(F(" seconds (or "));
    Serial.print(samplingRateMils);
    Serial.println(F(" ms)..."));
    delay(samplingRateMils);
}

void moduleSetup() {

    fonaSS.begin(115200); // Default SIM7000 shield baud rate

    Serial.println(F("Configuring to 9600 baud"));
    fonaSS.println("AT+IPR=9600"); // Set baud rate
    delay(100); // Short pause to let the command run
    fonaSS.begin(9600);
    if (! fona.begin(fonaSS)) {
        Serial.println(F("Couldn't find FONA"));
        resetFunc();
    }

    type = fona.type();
    Serial.println(F("FONA is OK"));
    Serial.print(F("Found "));
    switch (type) {
    case SIM800L:
        Serial.println(F("SIM800L"));
        break;
    case SIM800H:
        Serial.println(F("SIM800H"));
        break;
    case SIM808_V1:
        Serial.println(F("SIM808 (v1)"));
        break;
    case SIM808_V2:
        Serial.println(F("SIM808 (v2)"));
        break;
    case SIM5320A:
        Serial.println(F("SIM5320A (American)"));
        break;
    case SIM5320E:
        Serial.println(F("SIM5320E (European)"));
        break;
    case SIM7000:
        Serial.println(F("SIM7000"));
        break;
    case SIM7070:
        Serial.println(F("SIM7070"));
        break;
    case SIM7500:
        Serial.println(F("SIM7500"));
        break;
    case SIM7600:
        Serial.println(F("SIM7600"));
        break;
    default:
        Serial.println(F("???"));
        break;
    }

    // Print module IMEI number.
    uint8_t imeiLen = fona.getIMEI(imei);
    if (imeiLen > 0) {
        Serial.print("Module IMEI: ");
        Serial.println(imei);
    }
}

// Read the module's power supply voltage
float readVcc() {
    // Read battery voltage
    if (!fona.getBattVoltage(&battLevel)) Serial.println(F("Failed to read batt"));
    else Serial.print(F("battery = "));
    Serial.print(battLevel);
    Serial.println(F(" mV"));

    return battLevel;
}

bool netStatus() {
    int n = fona.getNetworkStatus();

    Serial.print(F("Network status "));
    Serial.print(n);
    Serial.print(F(": "));
    if (n == 0) Serial.println(F("Not registered"));
    if (n == 1) Serial.println(F("Registered (home)"));
    if (n == 2) Serial.println(F("Not registered (searching)"));
    if (n == 3) Serial.println(F("Denied"));
    if (n == 4) Serial.println(F("Unknown"));
    if (n == 5) Serial.println(F("Registered roaming"));

    if (n == 3) {
        resetFunc();    //In the event of a denial, reboot
    }

    if (!(n == 1 || n == 5)) {
        return false;
    }
    else {
      return true;
    }
}

// Turn off the MCU completely. Can only wake up from RESET button
// However, this can be altered to wake up via a pin change interrupt
void MCU_powerDown() {
    set_sleep_mode(SLEEP_MODE_PWR_DOWN);
    ADCSRA = 0; // Turn off ADC
    power_all_disable ();  // Power off ADC, Timer 0 and 1, serial interface
    sleep_enable();
    sleep_cpu();
}