// -----------------------------------------------------------------------------
// DHTXX Sensor
// Copyright (C) 2017-2019 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && DHT_SUPPORT

#pragma once

#include "Arduino.h"
#include "BaseSensor.h"

constexpr const double DHT_DUMMY_VALUE = -255;
constexpr const size_t DHT_MAX_DATA = 5;
constexpr const size_t DHT_MAX_ERRORS = 5;
constexpr const uint32_t DHT_MIN_INTERVAL = 2000;

enum class DHTChipType {
    DHT11,
    DHT12,
    DHT21,
    DHT22,
    AM2301,
    SI7021
};

// Note: backwards compatibility for configuration headers
#define DHT_CHIP_DHT11              DHTChipType::DHT11
#define DHT_CHIP_DHT12              DHTChipType::DHT12
#define DHT_CHIP_DHT22              DHTChipType::DHT22
#define DHT_CHIP_DHT21              DHTChipType::DHT21
#define DHT_CHIP_AM2301             DHTChipType::AM2301
#define DHT_CHIP_SI7021             DHTChipType::SI7021

int dhtchip_to_number(DHTChipType chip) {
    switch (chip) {
        case DHTChipType::DHT11:
            return 11;
        case DHTChipType::DHT12:
            return 12;
        case DHTChipType::DHT21:
        case DHTChipType::AM2301:
            return 21;
        case DHTChipType::DHT22:
        case DHTChipType::SI7021:
            return 22;
        default:
            return -1;
    }
}

class DHTSensor : public BaseSensor {

    public:

        // ---------------------------------------------------------------------
        // Public
        // ---------------------------------------------------------------------

        DHTSensor(): BaseSensor() {
            _count = 2;
            _sensor_id = SENSOR_DHTXX_ID;
        }

        ~DHTSensor() {
            if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
        }

        // ---------------------------------------------------------------------

        void setGPIO(unsigned char gpio) {
            _gpio = gpio;
        }

        void setType(DHTChipType type) {
            _type = type;
        }

        // ---------------------------------------------------------------------

        unsigned char getGPIO() {
            return _gpio;
        }

        int getType() {
            return dhtchip_to_number(_type);
        }

        DHTChipType getChipType() {
            return _type;
        }

        // ---------------------------------------------------------------------
        // Sensor API
        // ---------------------------------------------------------------------

        // Initialization method, must be idempotent
        void begin() {

            _count = 0;

            // Manage GPIO lock
            if (_previous != GPIO_NONE) gpioReleaseLock(_previous);
            _previous = GPIO_NONE;
            if (!gpioGetLock(_gpio)) {
                _error = SENSOR_ERROR_GPIO_USED;
                return;
            }
            _previous = _gpio;

            // Set now to fail the check in _read at least once
            _last_ok = millis();

            _count = 2;
            _ready = true;

        }

        // Pre-read hook (usually to populate registers with up-to-date data)
        void pre() {
            _error = SENSOR_ERROR_OK;
            _read();
        }

        // Descriptive name of the sensor
        String description() {
            char buffer[20];
            snprintf(buffer, sizeof(buffer), "DHT%d @ GPIO%d", dhtchip_to_number(_type), _gpio);
            return String(buffer);
        }

        // Descriptive name of the slot # index
        String slot(unsigned char index) {
            return description();
        };

        // Address of the sensor (it could be the GPIO or I2C address)
        String address(unsigned char index) {
            return String(_gpio);
        }

        // Type for slot # index
        unsigned char type(unsigned char index) {
            if (index == 0) return MAGNITUDE_TEMPERATURE;
            if (index == 1) return MAGNITUDE_HUMIDITY;
            return MAGNITUDE_NONE;
        }

        // Current value for slot # index
        double value(unsigned char index) {
            if (index == 0) return _temperature;
            if (index == 1) return _humidity;
            return 0;
        }

    protected:

        // ---------------------------------------------------------------------
        // Protected
        // ---------------------------------------------------------------------

        void _read() {

            if ((_last_ok > 0) && (millis() - _last_ok < DHT_MIN_INTERVAL)) {
                if ((_temperature == DHT_DUMMY_VALUE) && (_humidity == DHT_DUMMY_VALUE)) {
                    _error = SENSOR_ERROR_WARM_UP;
                } else {
                    _error = SENSOR_ERROR_OK;
                }
                return;
            }

            unsigned long low = 0;
            unsigned long high = 0;

            unsigned char dhtData[DHT_MAX_DATA] = {0};
            unsigned char byteInx = 0;
            unsigned char bitInx = 7;

            pinMode(_gpio, OUTPUT);

        	// Send start signal to DHT sensor
        	if (++_errors > DHT_MAX_ERRORS) {
                _errors = 0;
                digitalWrite(_gpio, HIGH);
                nice_delay(250);
            }
            noInterrupts();
        	digitalWrite(_gpio, LOW);
            if ((_type == DHT_CHIP_DHT11) || (_type == DHT_CHIP_DHT12)) {
                nice_delay(20);
            } else if (_type == DHT_CHIP_SI7021) {
                delayMicroseconds(500);
            } else {
                delayMicroseconds(1100);
            }
            digitalWrite(_gpio, HIGH);
            delayMicroseconds(40);
            pinMode(_gpio, INPUT_PULLUP);
            delayMicroseconds(10);

        	// No errors, read the 40 data bits
        	for( int k = 0; k < 41; k++ ) {

        		// Starts new data transmission with >50us low signal
        		low = _signal(100, LOW);
        		if (low == 0) {
                    _error = SENSOR_ERROR_TIMEOUT;
                    return;
                }

        		// Check to see if after >70us rx data is a 0 or a 1
        		high = _signal(100, HIGH);
                if (high == 0) {
                    _error = SENSOR_ERROR_TIMEOUT;
                    return;
                }

                // Skip the first bit
                if (k == 0) continue;

        		// add the current read to the output data
        		// since all dhtData array where set to 0 at the start,
        		// only look for "1" (>28us us)
        		if (high > low) dhtData[byteInx] |= (1 << bitInx);

        		// index to next byte
        		if (bitInx == 0) {
                    bitInx = 7;
                    ++byteInx;
                } else {
            		--bitInx;
                }

        	}

            interrupts();

            // Verify checksum
            if (dhtData[4] != ((dhtData[0] + dhtData[1] + dhtData[2] + dhtData[3]) & 0xFF)) {
                _error = SENSOR_ERROR_CRC;
                return;
            }

        	// Get humidity from Data[0] and Data[1]
            if (_type == DHT_CHIP_DHT11) {
                _humidity = dhtData[0];
            } else if (_type == DHT_CHIP_DHT12) {
                _humidity = dhtData[0];
				_humidity += dhtData[1] * 0.1;
            } else {
        	    _humidity = dhtData[0] * 256 + dhtData[1];
        	    _humidity /= 10;
            }

        	// Get temp from Data[2] and Data[3]
            if (_type == DHT_CHIP_DHT11) {
                _temperature = dhtData[2];
			} else if (_type == DHT_CHIP_DHT12) {
				_temperature = (dhtData[2] & 0x7F);
				_temperature += dhtData[3] * 0.1;
				if (dhtData[2] & 0x80) _temperature *= -1;
            } else {
                _temperature = (dhtData[2] & 0x7F) * 256 + dhtData[3];
                _temperature /= 10;
                if (dhtData[2] & 0x80) _temperature *= -1;
            }

            _last_ok = millis();
            _errors = 0;
            _error = SENSOR_ERROR_OK;

        }

        unsigned long _signal(unsigned long usTimeOut, bool state) {
        	unsigned long uSec = 1;
        	while (digitalRead(_gpio) == state) {
                if (++uSec > usTimeOut) return 0;
                delayMicroseconds(1);
        	}
        	return uSec;
        }

        DHTChipType _type = DHT_CHIP_DHT22;

        unsigned char _gpio = GPIO_NONE;
        unsigned char _previous = GPIO_NONE;

        unsigned long _last_ok = 0;
        unsigned char _errors = 0;

        double _temperature = DHT_DUMMY_VALUE;
        double _humidity = DHT_DUMMY_VALUE;

};

#endif // SENSOR_SUPPORT && DHT_SUPPORT