//
//    FILE: MTP40F.cpp
//  AUTHOR: Rob Tillaart
//    DATE: 2023-07-25
// VERSION: 0.2.1
// PURPOSE: Arduino library for MTP40F CO2 sensor
//     URL: https://github.com/RobTillaart/MTP40F


#include "MTP40F.h"

//  debug flag, development.
//  #define MTP40F_DEBUG    1


MTP40F::MTP40F()
{
  init();
}


MTP40F::MTP40F(Stream * stream)
{
  init();
  _ser = stream;
}


void MTP40F::init()
{
  _buffer[0] = '\0';
  _type = 5;
}


void MTP40F::setStream(Stream * stream)
{
  init();
  _ser = stream;
}

bool MTP40F::begin()
{
  _timeout       = 100;
  _lastRead      = 0;
  _airPressureReference = 0;
  _gasLevel      = 0;
  _suppressError = false;
  _lastError     = MTP40F_OK;
  return true;
}


int MTP40F::getAirPressureReference()
{
  _lastError = MTP40F_OK;

  uint8_t cmd[9] = { 0x42, 0x4D, 0xA0, 0x00, 0x02, 0x00, 0x00, 0x01, 0x31 };
  if (request(cmd, 9, 11))
  {
    _airPressureReference = _buffer[7] * 256 + _buffer[8];
    return _airPressureReference;
  }

  _lastError = MTP40F_INVALID_AIR_PRESSURE;
  if (_suppressError) return _airPressureReference;
  return _lastError;
}


bool MTP40F::setAirPressureReference(int apr)
{
  if ((apr < 700) || (apr > 1100))  //  page 5 datasheet
  {
    return false;
  }

  uint8_t cmd[11] = { 0x42, 0x4D, 0xA0, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00 };
  cmd[7] = apr / 256;
  cmd[8] = apr % 256;
  if (request(cmd, 11, 11) )
  {
    return true;
  }
  return false;
}


uint32_t MTP40F::getGasConcentration()
{
  _lastError = MTP40F_OK;

  //  max read freq 1x per 2 seconds
  //  datasheet measurement interval = 2s
  if (millis() - _lastRead < 2000)
  {
    return _gasLevel;  //  last value
  }
  _lastRead = millis();

  uint8_t cmd[9] = { 0x42, 0x4D, 0xA0, 0x00, 0x03, 0x00, 0x00, 0x01, 0x32 };
  if (request(cmd, 9, 14) )
  {
    uint8_t status = _buffer[11];
    if (status == 0x00) 
    {
      _gasLevel =  _buffer[7];
      _gasLevel <<= 8;
      _gasLevel |= _buffer[8];
      _gasLevel <<= 8;
      _gasLevel |= _buffer[9];
      _gasLevel <<= 8;
      _gasLevel += _buffer[10];
      return _gasLevel;
    }
    _lastError = MTP40F_INVALID_GAS_LEVEL;
    if (_suppressError) return _gasLevel;  //  last level
    return _lastError;
  }

  _lastError = MTP40F_REQUEST_FAILED;
  if (_suppressError) return _gasLevel;  //  last level
  return _lastError;
}


bool MTP40F::setSinglePointCorrection(uint32_t spc)
{
  if ((spc < 400) || (spc > 2000))  //  datasheet unclear 0x2000???
  {
    return false;
  }

  uint8_t cmd[13] = { 0x42, 0x4D, 0xA0, 0x00, 0x04, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
  cmd[7]  = 0;
  cmd[8]  = 0;
  cmd[9]  = spc / 256;
  cmd[10] = spc % 256;
  if (request(cmd, 13, 10) )
  {
    if (_buffer[7] ) return true;
  }
  return false;
}


bool MTP40F::getSinglePointCorrectionReady()
{
  uint8_t cmd[9] = { 0x42, 0x4D, 0xA0, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00 };
  if (request(cmd, 9, 10) )
  {
    if (_buffer[8] == 0) return true;
  }
  return false;
}


bool MTP40F::openSelfCalibration()
{
  uint8_t cmd[10] = { 0x42, 0x4D, 0xA0, 0x00, 0x06, 0x00, 0x01, 0x00, 0x00, 0x00 };
  if (request(cmd, 10, 9) )
  {
    return true;
  }
  return false;
}


bool MTP40F::closeSelfCalibration()
{
  uint8_t cmd[10] = { 0x42, 0x4D, 0xA0, 0x00, 0x06, 0x00, 0x01, 0xFF, 0x00, 0x00 };
  if (request(cmd, 10, 9) )
  {
    return true;
  }
  return false;
}


uint8_t MTP40F::getSelfCalibrationStatus()
{
  uint8_t cmd[9] = { 0x42, 0x4D, 0xA0, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00 };
  if (request(cmd, 9, 10) )
  {
    return _buffer[7];   //  0x00 or 0xFF
  }
  _lastError = MTP40F_REQUEST_FAILED;
  return _lastError;
}


uint16_t MTP40F::getSelfCalibrationHours()
{
  uint8_t cmd[9] = { 0x42, 0x4D, 0xA0, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00 };
  if (request(cmd, 9, 11) )
  {
    uint16_t hours = _buffer[7] * 256 + _buffer[8];
    return hours;
  }
  _lastError = MTP40F_REQUEST_FAILED;
  return _lastError;
}


bool MTP40F::setSelfCalibrationHours(uint16_t hours)
{
  if ((hours < 24) || (hours > 720))
  {
    return false;
  }
  uint8_t cmd[11] = { 0x42, 0x4D, 0xA0, 0x00, 0x09, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00 };
  cmd[7] = hours / 256;
  cmd[8] = hours & 0xFF;
  if (request(cmd, 11, 10) )
  {
    return (_buffer[7] == 0x00);
  }
  return false;
}


int MTP40F::lastError()
{
  int e = _lastError;
  _lastError = MTP40F_OK;
  return e;
}


//////////////////////////////////////////////////////////////////////
//
//  PROTECTED
//
bool MTP40F::request(uint8_t *data, uint8_t commandLength, uint8_t responseLength)
{
  if (_ser == NULL) {
    _lastError = MTP40F_NO_STREAM;
    return false;
  }

  //  calculate CRC of command
  uint16_t crc = CRC(data, commandLength - 2);
  data[commandLength - 2] = crc / 256;
  data[commandLength - 1] = crc & 0xFF;
  while (commandLength--)
  {
#ifdef MTP40F_DEBUG
    if (*data < 0x10) Serial.print(0);
    Serial.print(*data++, HEX);
    Serial.print(" ");
#else
    _ser->write(*data++);
#endif
     yield();   //  because baud rate is low!
  }

  uint32_t start = millis();
  uint8_t i = 0;
  while (i < responseLength)
  {
    if (millis() - start > _timeout) return false;
    if (_ser->available())
    {
      _buffer[i++] = _ser->read();
    }
    yield();  //  because baud rate is low!
  }
  if (responseLength > 2)
  {
    uint16_t expected_crc = (_buffer[responseLength - 2] << 8) | _buffer[responseLength - 1];
    uint16_t calc_crc = CRC(_buffer, responseLength - 2);
    if (calc_crc != expected_crc)
    {
      _lastError = MTP40F_INVALID_CRC;
      return false;
    }
    return calc_crc == expected_crc;
  }

  return true;
}


uint16_t MTP40F::CRC(uint8_t *data, uint16_t length)
{
  uint16_t sum = 0;
  for (uint16_t i= 0; i < length; i++)
  {
    sum += *data++;
  }
  return sum;
}


//  -- END OF FILE --

