// -----------------------------------------------------------------------------
// AM2320 Humidity & Temperature sensor over I2C
// Copyright (C) 2018 by Mustafa Tufan
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && AM2320_SUPPORT

#pragma once

#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support.

#include "Arduino.h"
#include "I2CSensor.h"

// https://akizukidenshi.com/download/ds/aosong/AM2320.pdf
#define AM2320_I2C_READ_REGISTER_DATA        0x03    // Read one or more data registers
#define AM2320_I2C_WRITE_MULTIPLE_REGISTERS  0x10    // Multiple sets of binary data to write multiple registers
/*
Register         | Address | Register           | Address | Register                | Address | Register  | Address
-----------------+---------+--------------------+---------+-------------------------+---------+-----------+--------
High humidity    | 0x00    | Model High         | 0x08    | Users register a high   | 0x10    | Retention | 0x18
Low humidity     | 0x01    | Model Low          | 0x09    | Users register a low    | 0x11    | Retention | 0x19
High temperature | 0x02    | The version number | 0x0A    | Users register 2 high   | 0x12    | Retention | 0x1A
Low temperature  | 0x03    | Device ID(24-31)Bit| 0x0B    | Users register 2 low    | 0x13    | Retention | 0x1B
Retention        | 0x04    | Device ID(24-31)Bit| 0x0C    | Retention               | 0x14    | Retention | 0x1C
Retention        | 0x05    | Device ID(24-31)Bit| 0x0D    | Retention               | 0x15    | Retention | 0x1D
Retention        | 0x06    | Device ID(24-31)Bit| 0x0E    | Retention               | 0x16    | Retention | 0x1E
Retention        | 0x07    | Status Register    | 0x0F    | Retention               | 0x17    | Retention | 0x1F
*/

class AM2320Sensor : public I2CSensor {

    public:

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

        AM2320Sensor(): I2CSensor() {
            _count = 2;
            _sensor_id = SENSOR_AM2320_ID;
        }

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

        // Initialization method, must be idempotent
        void begin() {
            if (!_dirty) return;

            // I2C auto-discover
            unsigned char addresses[] = {0x23, 0x5C, 0xB8};
            _address = _begin_i2c(_address, sizeof(addresses), addresses);
            if (_address == 0) return;

            _ready = true;
            _dirty = false;
        }

        // Descriptive name of the sensor
        String description() {
            char buffer[25];
            snprintf(buffer, sizeof(buffer), "AM2320 @ I2C (0x%02X)", _address);
            return String(buffer);
        }

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

        // 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;
        }

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

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

    protected:

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

/*
        // Get device model, version, device_id

        void _init() {
            i2c_wakeup(_address);
            delayMicroseconds(800);

            unsigned char _buffer[11];

            // 0x08 = read address
            //    7 = number of bytes to read
            if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x08, 7) != I2C_TRANS_SUCCESS) {
                _error = SENSOR_ERROR_TIMEOUT;
                return false;
            }

            uint16_t model     = (_buffer[2] << 8) | _buffer[3];
            uint8_t  version   = _buffer[4];
            uint32_t device_id = _buffer[8] << 24 | _buffer[7] << 16 | _buffer[6] << 8 | _buffer[5];
        }
*/

        void _read() {

            i2c_wakeup(_address);

            // waiting time of at least 800 μs, the maximum 3000 μs
            delayMicroseconds(800); // just to be on safe side

            // 0x00 = read address
            //    4 = number of bytes to read
            if (i2c_write_uint8(_address, AM2320_I2C_READ_REGISTER_DATA, 0x00, 4) != I2C_TRANS_SUCCESS) {
                _error = SENSOR_ERROR_TIMEOUT;
                return;
            }

            unsigned char _buffer[8];

            // waiting time of at least 800 μs, the maximum 3000 μs
            delayMicroseconds(800 + ((3000-800)/2) );
            i2c_read_buffer(_address, _buffer, 8);

            // Humidity   : 01F4 = (1×256)+(F×16)+4 = 500 => humidity = 500÷10 = 50.0 %
            //              0339 = (3×256)+(3×16)+9 = 825 => humidity = 825÷10 = 82.5 %
            // Temperature: 00FA =         (F×16)+A = 250 => temperature = 250÷10 = 25.0 C
            //              0115 = (1×256)+(1×16)+5 = 277 => temperature = 277÷10 = 27.7 C
            // Temperature resolution is 16Bit, temperature highest bit (Bit 15) is equal to 1 indicates a negative temperature

            // _buffer 0 = function code
            // _buffer 1 = number of bytes
            // _buffer 2-3 = high/low humidity
            // _buffer 4-5 = high/low temperature
            // _buffer 6-7 = CRC low/high

            unsigned int responseCRC = 0;
            responseCRC = ((responseCRC | _buffer[7]) << 8 | _buffer[6]);

            if (responseCRC == _CRC16(_buffer)) {
                int foo = (_buffer[2] << 8) | _buffer[3];
                _humidity = foo / 10.0;

                foo = ((_buffer[4] & 0x7F) << 8) | _buffer[5];  // clean bit 15 and merge
                _temperature = foo / 10.0;

                if (_buffer[4] & 0x80) {                // is bit 15 == 1
                    _temperature = _temperature * -1;   // negative temperature
                }

                _error = SENSOR_ERROR_OK;
            } else {
                _error = SENSOR_ERROR_CRC;
                return;
            }
        }

        unsigned int _CRC16(unsigned char buffer[]) {
            unsigned int crc16 = 0xFFFF;

            for (unsigned int i = 0; i < 6; i++) {
                crc16 ^= buffer[i];

                for (unsigned int b = 8; b != 0; b--) {
                    if (crc16 & 0x01) {    // is lsb set
                        crc16 >>= 1;
                        crc16 ^= 0xA001;
                    } else {
                        crc16 >>= 1;
                    }
                }
            }

            return crc16;
        }

        double _temperature = 0;
        double _humidity = 0;
};

#endif // SENSOR_SUPPORT && AM2320_SUPPORT
