/** \mainpage DHT12 sensor library
 * DHT12 Sensor Library
 * https://www.mischianti.org/2019/01/01/dht12-library-en/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Renzo Mischianti www.mischianti.org All right reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "DHT12.h"
#include "Wire.h"

// Default is i2c on default pin with default DHT12 address
DHT12::DHT12(void) {
	_wire = &Wire;
}

DHT12::DHT12(uint8_t addressOrPin, bool oneWire) {
	_isOneWire = oneWire;
	if (oneWire) {
		_wire = NULL;
		_pin = addressOrPin;
		#if !defined(__AVR) && !defined(__STM32F1__) && !defined(TEENSYDUINO)
			_bit = digitalPinToBitMask(_pin);
			_port = digitalPinToPort(_pin);
		#endif
		_maxcycles = microsecondsToClockCycles(1000); // 1 millisecond timeout for
													  // reading pulses from DHT sensor.
		// Note that count is now ignored as the DHT reading algorithm adjusts itself
		// basd on the speed of the processor.

		DEBUG_PRINTLN("PIN MODE");
	} else {
		_wire = &Wire;
		_address = addressOrPin;
		DEBUG_PRINTLN("I2C MODE");
	}
}

// Is not good idea use other pin for i2c as standard on Arduino you can get lag.
// The lag happens when you choose "different pins", because you are then using a
// slow software emulation of the I2C hardware. The built in I2C hardware has fixed pin assignments.
#if !defined(__AVR) && !defined(__STM32F1__) && !defined(TEENSYDUINO)
	DHT12::DHT12(uint8_t sda, uint8_t scl) {
		_wire = &Wire;
		_isOneWire = false;
		_sda = sda;
		_scl = scl;
	}

	DHT12::DHT12(uint8_t sda, uint8_t scl, uint8_t address) {
		_wire = &Wire;
		_isOneWire = false;
		_sda = sda;
		_scl = scl;
		_address = address;
	}

	#ifdef ESP32

		///// changes for second i2c bus

		DHT12::DHT12(TwoWire *pWire) {
			_wire = pWire;
			_isOneWire = false;
		}

		DHT12::DHT12(TwoWire *pWire, uint8_t address) {
			_wire = pWire;
			_isOneWire = false;

			_address = address;
		}
		DHT12::DHT12(TwoWire *pWire, uint8_t sda, uint8_t scl) {
			_wire = pWire;
			_isOneWire = false;
			_sda = sda;
			_scl = scl;
		}
		DHT12::DHT12(TwoWire *pWire, uint8_t sda, uint8_t scl, uint8_t address) {
			_wire = pWire;
			_isOneWire = false;
			_sda = sda;
			_scl = scl;
			_address = address;
		}
		// changes
	#endif
#endif

void DHT12::begin() {
	_lastreadtime = -(MIN_ELAPSED_TIME + 1);
	if (_isOneWire) {
		// set up the pins!
		pinMode(_pin, INPUT_PULLUP);
		// Using this value makes sure that millis() - lastreadtime will be
		// >= MIN_INTERVAL right away. Note that this assignment wraps around,
		// but so will the subtraction.
		DEBUG_PRINT("Max clock cycles: ");
		DEBUG_PRINTLN(_maxcycles, DEC);
	} else {
		#if !defined(__AVR) && !defined(__STM32F1__) && !defined(TEENSYDUINO)
			_wire->begin(_sda, _scl);
		#else
			//	Default pin for AVR some problem on software emulation
			//	#define SCL_PIN _scl
			// #define SDA_PIN _sda
			_wire->begin();
		#endif
		DEBUG_PRINT("I2C Inizialization: sda, scl: ");
		DEBUG_PRINT(_sda);
		DEBUG_PRINT(",");
		DEBUG_PRINTLN(_scl);
	}
}

DHT12::ReadStatus DHT12::readStatus(bool force) {
	// Check if sensor was read less than two seconds ago and return early
	// to use last reading.
	uint32_t currenttime = millis();
	if (!force && ((currenttime - _lastreadtime) < MIN_ELAPSED_TIME)) {
		return _lastresult; // return last correct measurement
	}
	_lastreadtime = currenttime;

	if (_isOneWire) {
		// Reset 40 bits of received data to zero.
		data[0] = data[1] = data[2] = data[3] = data[4] = 0;

		// Send start signal.  See DHT datasheet for full signal diagram:
		//   http://www.adafruit.com/datasheets/Digital%20humidity%20and%20temperature%20sensor%20AM2302.pdf

		  // Go into high impedence state to let pull-up raise data line level and
		  // start the reading process.
		  digitalWrite(_pin, HIGH);
		  delay(250);

		  // First set data line low for 20 milliseconds.
		  pinMode(_pin, OUTPUT);
		  digitalWrite(_pin, LOW);
		  delay(20);

		  uint32_t cycles[80];
		  {
		    // Turn off interrupts temporarily because the next sections are timing critical
		    // and we don't want any interruptions.
			  InterruptLockDht12 lock;

		    // End the start signal by setting data line high for 40 microseconds.
		    digitalWrite(_pin, HIGH);
		    delayMicroseconds(40);

		    // Now start reading the data line to get the value from the DHT sensor.
		    pinMode(_pin, INPUT_PULLUP);
		    delayMicroseconds(10);  // Delay a bit to let sensor pull data line low.

			// First expect a low signal for ~80 microseconds followed by a high signal
			// for ~80 microseconds again.
			if (expectPulse(LOW) == 0) {
				DEBUG_PRINTLN(F("Timeout waiting for start signal low pulse."));
				_lastresult = ERROR_TIMEOUT_LOW;
				return _lastresult;
			}
			if (expectPulse(HIGH) == 0) {
				DEBUG_PRINTLN(F("Timeout waiting for start signal high pulse."));
				_lastresult = ERROR_TIMEOUT_HIGH;
				return _lastresult;
			}

			// Now read the 40 bits sent by the sensor.  Each bit is sent as a 50
			// microsecond low pulse followed by a variable length high pulse.  If the
			// high pulse is ~28 microseconds then it's a 0 and if it's ~70 microseconds
			// then it's a 1.  We measure the cycle count of the initial 50us low pulse
			// and use that to compare to the cycle count of the high pulse to determine
			// if the bit is a 0 (high state cycle count < low state cycle count), or a
			// 1 (high state cycle count > low state cycle count). Note that for speed all
			// the pulses are read into a array and then examined in a later step.
			for (int i = 0; i < 80; i += 2) {
				cycles[i] = expectPulse(LOW);
				cycles[i + 1] = expectPulse(HIGH);
			}

			  // Inspect pulses and determine which ones are 0 (high state cycle count < low
			  // state cycle count), or 1 (high state cycle count > low state cycle count).
			  for (int i=0; i<40; ++i) {
			    uint32_t lowCycles  = cycles[2*i];
			    uint32_t highCycles = cycles[2*i+1];
			    if ((lowCycles == 0) || (highCycles == 0)) {
			      DEBUG_PRINTLN(F("Timeout waiting for pulse."));
			      _lastresult = ERROR_TIMEOUT;
			      return _lastresult;
			    }
			    data[i/8] <<= 1;
			    // Now compare the low and high cycle times to see if the bit is a 0 or 1.
			    if (highCycles > lowCycles) {
			      // High cycles are greater than 50us low cycle count, must be a 1.
			      data[i/8] |= 1;
			    }
			    // Else high cycles are less than (or equal to, a weird case) the 50us low
			    // cycle count so this must be a zero.  Nothing needs to be changed in the
			    // stored data.
			  }

			  DEBUG_PRINTLN(F("Received:"));
			  DEBUG_PRINT(data[0], HEX); DEBUG_PRINT(F(", "));
			  DEBUG_PRINT(data[1], HEX); DEBUG_PRINT(F(", "));
			  DEBUG_PRINT(data[2], HEX); DEBUG_PRINT(F(", "));
			  DEBUG_PRINT(data[3], HEX); DEBUG_PRINT(F(", "));
			  DEBUG_PRINT(data[4], HEX); DEBUG_PRINT(F(" =? "));
			  DEBUG_PRINTLN((data[0] + data[1] + data[2] + data[3]) & 0xFF, HEX);

				DHT12::ReadStatus cks = DHT12::_checksum();
				if (cks != OK) {
					DEBUG_PRINTLN("CHECKSUM ERROR!");
					_lastresult = cks;
					return cks;
				}

			_lastresult = OK;
			return OK;
		}
//		return DHT12::_readSensor(DHTLIB_DHT_WAKEUP, DHTLIB_DHT_LEADING_ZEROS);
//		return DHT12::_readSensor(DHTLIB_DHT11_WAKEUP, DHTLIB_DHT11_LEADING_ZEROS);

	} else {
		DEBUG_PRINT("I2C START READING..");
		_wire->beginTransmission(_address);
		_wire->write(0);
		if (_wire->endTransmission() != 0) {
			DEBUG_PRINTLN("CONNECTION ERROR!");
			_lastresult = ERROR_CONNECT;
			return _lastresult;
		}
		_wire->requestFrom(_address, (uint8_t) 5);
		for (uint8_t i = 0; i < 5; ++i) {
			data[i] = _wire->read();
			DEBUG_PRINTLN(data[i]);
		}

		delay(1);
		if (_wire->available() != 0) {
			DEBUG_PRINTLN("TIMEOUT ERROR!");
			_lastresult = ERROR_TIMEOUT;
			return _lastresult;
		}

		DHT12::ReadStatus cks = DHT12::_checksum();
		if (cks != OK) {
			DEBUG_PRINTLN("CHECKSUM ERROR!");
			_lastresult = cks;
			return cks;
		}

		DEBUG_PRINTLN("...READING OK");
		_lastresult = OK;
		return _lastresult;
	}
}

bool DHT12::read(bool force) {
	ReadStatus chk = DHT12::readStatus(force);
	DEBUG_PRINT(F("\nRead sensor: "));
	DEBUG_PRINT((chk != DHT12::OK));
	switch (chk) {
	case DHT12::OK:
		DEBUG_PRINTLN(F("OK"));
		break;
	case DHT12::ERROR_CHECKSUM:
		DEBUG_PRINTLN(F("Checksum error"))
		;
		break;
	case DHT12::ERROR_TIMEOUT:
		DEBUG_PRINTLN(F("Timeout error"))
		;
		break;
	case DHT12::ERROR_TIMEOUT_HIGH:
		DEBUG_PRINTLN(F("Timeout error high"))
		;
		break;
	case DHT12::ERROR_TIMEOUT_LOW:
		DEBUG_PRINTLN(F("Timeout error low"))
		;
		break;
	case DHT12::ERROR_CONNECT:
		DEBUG_PRINTLN(F("Connect error"))
		;
		break;
	case DHT12::ERROR_ACK_L:
		DEBUG_PRINTLN(F("AckL error"))
		;
		break;
	case DHT12::ERROR_ACK_H:
		DEBUG_PRINTLN(F("AckH error"))
		;
		break;
	case DHT12::ERROR_UNKNOWN:
		DEBUG_PRINTLN(F("Unknown error DETECTED"))
		;
		break;
	case DHT12::NONE:
		DEBUG_PRINTLN(F("No result"))
		;
		break;
	default:
		DEBUG_PRINTLN(F("Unknown error"))
		;
		break;
	}
	return (chk == DHT12::OK);
}

float DHT12::convertCtoF(float c) {
	return c * 1.8 + 32;
}

float DHT12::convertFtoC(float f) {
	return (f - 32) * 0.55555;
}

// boolean isFahrenheit: True == Fahrenheit; False == Celsius
float DHT12::computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit) {
	// Using both Rothfusz and Steadman's equations
	// http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
	float hi;

	if (!isFahrenheit)
		temperature = convertCtoF(temperature);

	hi = 0.5
			* (temperature + 61.0 + ((temperature - 68.0) * 1.2)
					+ (percentHumidity * 0.094));

	if (hi > 79) {
		hi = -42.379 + 2.04901523 * temperature + 10.14333127 * percentHumidity
				+ -0.22475541 * temperature * percentHumidity
				+ -0.00683783 * pow(temperature, 2)
				+ -0.05481717 * pow(percentHumidity, 2)
				+ 0.00122874 * pow(temperature, 2) * percentHumidity
				+ 0.00085282 * temperature * pow(percentHumidity, 2)
				+ -0.00000199 * pow(temperature, 2) * pow(percentHumidity, 2);

		if ((percentHumidity < 13) && (temperature >= 80.0)
				&& (temperature <= 112.0))
			hi -= ((13.0 - percentHumidity) * 0.25)
					* sqrt((17.0 - abs(temperature - 95.0)) * 0.05882);

		else if ((percentHumidity > 85.0) && (temperature >= 80.0)
				&& (temperature <= 87.0))
			hi += ((percentHumidity - 85.0) * 0.1)
					* ((87.0 - temperature) * 0.2);
	}

	return isFahrenheit ? hi : convertFtoC(hi);
}

float DHT12::readHumidity(bool force) {
	DEBUG_PRINTLN("----------------------------");
	float humidity = NAN;

	if (_isOneWire) {
		if (DHT12::read(force)) {
			DEBUG_PRINT(data[0]);
//			humidity = data[0];
			humidity = (data[0] + (float) data[1] / 10);
		}
	} else {
		if (DHT12::read(force)) {
			humidity = (data[0] + (float) data[1] / 10);
		}
	}
	return humidity;
}

// boolean S == Scale.  True == Fahrenheit; False == Celsius
float DHT12::readTemperature(bool scale, bool force) {
	float temperature = NAN;
	if (_isOneWire) {
		if (DHT12::read(force)) {
//			temperature = data[2];
//			if (scale) {
//				temperature = convertCtoF(temperature);
//			}
			byte scaleValue = data[3] & B01111111;
			byte signValue  = data[3] & B10000000;

			temperature = (data[2] + (float) scaleValue / 10);// ((data[2] & 0x7F)*256 + data[3]);
			    if (signValue)  // negative temperature
			        temperature = -temperature;

			if (scale) {
				temperature = convertCtoF(temperature);
			}
		}
	} else {

		bool r = DHT12::read(force);
		DEBUG_PRINT("READ ---> ");
		DEBUG_PRINTLN(r);
		if (r) {
			DEBUG_PRINT("BIT 0 -> ");
			DEBUG_PRINTLN(data[0], BIN);
			DEBUG_PRINT("BIT 1 -> ");
			DEBUG_PRINTLN(data[1], BIN);
			DEBUG_PRINT("BIT 2 -> ");
			DEBUG_PRINTLN(data[2], BIN);
			DEBUG_PRINT("BIT 3 -> ");
			DEBUG_PRINTLN(data[3], BIN);
			DEBUG_PRINT("BIT 4 -> ");
			DEBUG_PRINTLN(data[4], BIN);
			DEBUG_PRINT("BIT 5 -> ");
			DEBUG_PRINTLN(data[5], BIN);

			byte scaleValue = data[3] & B01111111;
			byte signValue  = data[3] & B10000000;

			temperature = (data[2] + (float) scaleValue / 10);// ((data[2] & 0x7F)*256 + data[3]);
			    if (signValue)  // negative temperature
			        temperature = -temperature;

			if (scale) {
				temperature = convertCtoF(temperature);
			}
		}
	}
	return temperature;

}

#include <math.h>
// dewPoint function NOAA
// reference (1) : http://wahiduddin.net/calc/density_algorithms.htm
// reference (2) : http://www.colorado.edu/geography/weather_station/Geog_site/about.htm
//
// boolean S == Scale.  True == Fahrenheit; False == Celsius
float DHT12::dewPoint(float temperature, float humidity, bool isFahrenheit) {
	// sloppy but good approximation for 0 ... +70 °C with max. deviation less than 0.25 °C

	float temp;

	if(!isFahrenheit){
		temp = temperature;
	} else {
		temp = convertFtoC(temperature);
	}

	float humi = humidity;
	float ans =  (temp - (14.55 + 0.114 * temp) * (1 - (0.01 * humi)) - pow(((2.5 + 0.007 * temp) * (1 - (0.01 * humi))),3) - (15.9 + 0.117 * temp) * pow((1 - (0.01 * humi)), 14));

	if(!isFahrenheit){
		return ans;           // returns dew Point in Celsius
	}

	return convertCtoF(ans);   // returns dew Point in Fahrenheit
}

//////// PRIVATE
DHT12::ReadStatus DHT12::_checksum() {
	uint8_t sum = data[0] + data[1] + data[2] + data[3];
	if (data[4] != sum)
		return ERROR_CHECKSUM;
	return OK;
}

// Expect the signal line to be at the specified level for a period of time and
// return a count of loop cycles spent at that level (this cycle count can be
// used to compare the relative time of two pulses).  If more than a millisecond
// ellapses without the level changing then the call fails with a 0 response.
// This is adapted from Arduino's pulseInLong function (which is only available
// in the very latest IDE versions):
//   https://github.com/arduino/Arduino/blob/master/hardware/arduino/avr/cores/arduino/wiring_pulse.c
uint32_t DHT12::expectPulse(bool level) {
	uint32_t count = 0;
	// On AVR platforms use direct GPIO port access as it's much faster and better
	// for catching pulses that are 10's of microseconds in length:
#if !defined(__AVR) && !defined(__STM32F1__) && !defined(TEENSYDUINO)
	uint8_t portState = level ? _bit : 0;
	while ((*portInputRegister(_port) & _bit) == portState) {
		if (count++ >= _maxcycles) {
			return 0; // Exceeded timeout, fail.
		}
	}
	// Otherwise fall back to using digitalRead (this seems to be necessary on ESP8266
	// right now, perhaps bugs in direct port access functions?).
#else
	while (digitalRead(_pin) == level) {
		if (count++ >= _maxcycles) {
			return 0; // Exceeded timeout, fail.
		}
	}
#endif

	return count;
}
