//
//    FILE: AM232X.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.5.1
// PURPOSE: AM232X library for AM2320 for Arduino.
//     URL: https://github.com/RobTillaart/AM232X


#include "AM232X.h"


const uint8_t AM232X_ADDRESS = 0x5C;


////////////////////////////////////////////////////////////////////
//
// PUBLIC
//
AM232X::AM232X(TwoWire *wire)
{
  _wire          = wire;
  //  reset() or begin() ?
  _humidity      = 0.0;
  _temperature   = 0.0;
  _humOffset     = 0.0;
  _tempOffset    = 0.0;
  _lastRead      = 0;
  _readDelay     = 2000;
  _suppressError = false;
}


bool AM232X::begin()
{
  if (! isConnected()) return false;
  this->read();
  return true;
}


bool AM232X::isConnected(uint16_t timeout)
{
  uint32_t start = micros();
  while (micros() - start < timeout)
  {
    _wire->beginTransmission(AM232X_ADDRESS);
    if ( _wire->endTransmission() == 0) return true;
    yield();
    delayMicroseconds(100);
  }
  return false;
}


int AM232X::read()
{
  if (millis() - _lastRead < _readDelay)
  {
    return AM232X_READ_TOO_FAST;
  }
  _lastRead = millis();

  //  READ HUMIDITY AND TEMPERATURE REGISTERS
  int rv = _readRegister(0x00, 4);
  if (rv < 0) return rv;

  if (rv != AM232X_OK)
  {
    if (_suppressError == false)
    {
      _humidity    = AM232X_INVALID_VALUE;
      _temperature = AM232X_INVALID_VALUE;
    }
    return rv;  //  propagate error value
  }

  //  EXTRACT HUMIDITY AND TEMPERATURE
  _humidity = (_bits[2] * 256 + _bits[3]) * 0.1;
  int16_t t = ((_bits[4] & 0x7F) * 256 + _bits[5]);
  if (t == 0)
  {
    _temperature = 0.0;     //  prevent -0.0;
  }
  else
  {
    _temperature = t * 0.1;
    if ((_bits[4] & 0x80) == 0x80 )
    {
      _temperature = -_temperature;
    }
  }

#ifdef AM232X_VALUE_OUT_OF_RANGE
  //  TEST OUT OF RANGE
  if (_humidity > 100)
  {
    return AM232X_HUMIDITY_OUT_OF_RANGE;
  }
  if ((_temperature < -40) || (_temperature > 80))
  {
    return AM232X_TEMPERATURE_OUT_OF_RANGE;
  }
#endif

  return AM232X_OK;
}


float AM232X::getHumidity()
{
  float _hum = _humidity;
  if (_humOffset != 0.0) _hum += _humOffset;
  return _hum;
}


float AM232X::getTemperature()
{
  float _tem = _temperature;
  if (_tempOffset != 0.0) _tem += _tempOffset;
  return _tem;
}


void AM232X::setReadDelay(uint16_t readDelay)
{
  _readDelay = readDelay;
  if (_readDelay == 0)
  {
    _readDelay = 2000;  //  reset
  }
};


int AM232X::getModel()
{
  int rv = _readRegister(0x08, 2);
  if (rv < 0) return rv;

  return (_bits[2] * 256) + _bits[3];
}


int AM232X::getVersion()
{
  int rv = _readRegister(0x0A, 1);
  if (rv < 0) return rv;

  return _bits[2];
}


uint32_t AM232X::getDeviceID()
{
  int rv = _readRegister(0x0B, 4);
  if (rv < 0) return rv;

  uint32_t _deviceID = (_bits[2] * 256) + _bits[3];
  _deviceID = _deviceID * 256 + _bits[4];
  _deviceID = _deviceID * 256 + _bits[5];
  return _deviceID;
}


int AM232X::getStatus()
{
  int rv = _readRegister(0x0F, 1);
  if (rv < 0) return rv;

  return _bits[2];
}


int AM232X::getUserRegisterA()
{
  int rv = _readRegister(0x10, 2);
  if (rv < 0) return rv;

  return (_bits[2] * 256) + _bits[3];
}


int AM232X::getUserRegisterB()
{
  int rv = _readRegister(0x12, 2);
  if (rv < 0) return rv;

  return (_bits[2] * 256) + _bits[3];
}


int AM232X::setStatus(uint8_t value)
{
  int rv = _writeRegister(0x0F, 1, value);
  if (rv < 0) return rv;

  return AM232X_OK;
}


int AM232X::setUserRegisterA(int value)
{
  int rv = _writeRegister(0x10, 2, value);
  if (rv < 0) return rv;

  return AM232X_OK;
}


int AM232X::setUserRegisterB(int value)
{
  int rv = _writeRegister(0x12, 2, value);
  if (rv < 0) return rv;

  return AM232X_OK;
}


////////////////////////////////////////////////////////////////////
//
//  PROTECTED
//
int AM232X::_readRegister(uint8_t reg, uint8_t count)
{
  //  HANDLE PENDING IRQ
  yield();

  //  WAKE UP the sensor
  if (! wakeUp() ) return AM232X_ERROR_CONNECT;

  //  request the data
  _wire->beginTransmission(AM232X_ADDRESS);
  _wire->write(0x03);
  _wire->write(reg);
  _wire->write(count);
  int rv = _wire->endTransmission();
  if (rv < 0) return rv;

  //  request 4 extra, 2 for command + 2 for CRC
  rv = _getData(count + 4);
  return rv;
}


int AM232X::_writeRegister(uint8_t reg, uint8_t count, int16_t value)
{
  if (! wakeUp() ) return AM232X_ERROR_CONNECT;

  //  prepare data to send
  _bits[0] = 0x10;
  _bits[1] = reg;
  _bits[2] = count;

  if (count == 2)
  {
    _bits[4] = value & 0xFF;
    _bits[3] = (value >> 8) & 0xFF;
  }
  else
  {
    _bits[3] = value & 0xFF;
  }

  //  send data
  uint8_t length = count + 3;  // 3 = command, startReg, #bytes
  _wire->beginTransmission(AM232X_ADDRESS);
  for (int i = 0; i < length; i++)
  {
    _wire->write(_bits[i]);
  }
  // send the CRC
  uint16_t crc = _crc16(_bits, length);
  _wire->write(crc & 0xFF);
  _wire->write(crc >> 8);

  int rv = _wire->endTransmission();
  if (rv < 0) return rv;

  // wait for the answer
  rv = _getData(length);
  return rv;
}


int AM232X::_getData(uint8_t length)
{
  int bytes = _wire->requestFrom(AM232X_ADDRESS, length);
  if (bytes == 0) return AM232X_ERROR_CONNECT;

  for (int i = 0; i < bytes; i++)
  {
    _bits[i] = _wire->read();
  }

  // ANALYZE ERRORS
  //   will not detect if we requested 1 byte as that will
  //   return 5 bytes as requested. E.g. getStatus()
  //   design a fix if it becomes a problem.
  if (bytes != length)
  {
    switch (_bits[3])
    {
      case 0x80: return AM232X_ERROR_FUNCTION;
      case 0x81: return AM232X_ERROR_ADDRESS;
      case 0x82: return AM232X_ERROR_REGISTER;
      case 0x83: return AM232X_ERROR_CRC_1;  // previous write had a wrong CRC
      case 0x84: return AM232X_ERROR_WRITE_DISABLED;
      default:   return AM232X_ERROR_UNKNOWN;
    }
  }

  // CRC is LOW Byte first
  uint16_t crc = _bits[bytes - 1] * 256 + _bits[bytes - 2];
  if (_crc16(&_bits[0], bytes - 2) != crc)
  {
    return AM232X_ERROR_CRC_2;  // read itself has wrong CRC
  }

  return AM232X_OK;
}


uint16_t AM232X::_crc16(uint8_t *ptr, uint8_t length)
{
  uint16_t crc = 0xFFFF;
  while(length--)
  {
    crc ^= *ptr++;
    for (uint8_t i = 0; i < 8; i++)
    {
      if (crc & 0x01)
      {
        crc >>= 1;
        crc ^= 0xA001;
      }
      else
      {
        crc >>= 1;
      }
    }
  }
  return crc;
}



/////////////////////////////////////////////////////////////////////////////
//
// AM232X derived classes
//
AM2320::AM2320(TwoWire *wire) : AM232X(wire)
{
}


AM2321::AM2321(TwoWire *wire) : AM232X(wire)
{
}


AM2322::AM2322(TwoWire *wire) : AM232X(wire)
{
}



// -- END OF FILE --

