//
//    FILE: I2C_SCANNER.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.3.1
//    DATE: 2022-08-29
// PURPOSE: Arduino class to implement an I2C scanner.
//     URL: https://github.com/RobTillaart/I2C_SCANNER


#include "I2C_SCANNER.h"


I2C_SCANNER::I2C_SCANNER(TwoWire *wire)
{
  _wire = wire;
}


//
//  CONFIGURATION
//
bool I2C_SCANNER::begin()
{
  _init();
  return true;
}


//
//  I2C PORT
//
uint8_t I2C_SCANNER::getWirePortCount()
{
  return _wirePortCount;
}


//  0 == Wire, 1 = Wire1 etc.
bool I2C_SCANNER::setWire(uint8_t n)
{
  if (n == 0) { _wire = &Wire; return true; };
#if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1
  if (n == 1) { _wire = &Wire1; return true; };
#endif
#if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2
  if (n == 2) { _wire = &Wire2; return true; };
#endif
#if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3
  if (n == 3) { _wire = &Wire3; return true; };
#endif
#if defined WIRE_IMPLEMENT_WIRE4 || WIRE_INTERFACES_COUNT > 4
  if (n == 4) { _wire = &Wire4; return true; };
#endif
#if defined WIRE_IMPLEMENT_WIRE5 || WIRE_INTERFACES_COUNT > 5
  if (n == 5) { _wire = &Wire5; return true; };
#endif
  return false;
}


bool I2C_SCANNER::setWire(TwoWire *wire)
{
  _wire = wire;
  return true;
}


TwoWire* I2C_SCANNER::getWire()
{
  return _wire;
}


//
//  RESET
//
int I2C_SCANNER::softwareReset(uint8_t method)
{
  //  only support 0 and 1
  if (method > 1) return -999;
  if (method == 1)
  {
    //  from https://github.com/RobTillaart/PCA9634/issues/10#issuecomment-1206326417
   const uint8_t SW_RESET = 0x03;
   _wire->beginTransmission(SW_RESET);
   _wire->write(0xA5);
   _wire->write(0x5A);
   return _wire->endTransmission(true);
  }

  //  default
  //  based upon NXP specification - UM10204.pdf - page 16
  _wire->beginTransmission(0x00);
  _wire->write(0x06);
  return _wire->endTransmission(true);
}


//
//  TIMING
//
bool I2C_SCANNER::setClock(uint32_t clockFrequency)
{
  _wire->setClock(clockFrequency);
  return true;
}


#if defined(ESP32)
uint32_t I2C_SCANNER::getClock()
{
  return _wire->getClock();
}
#endif


//
//  SCANNING
//
uint16_t I2C_SCANNER::ping(uint8_t address, uint16_t count)
{
  if (count == 0) count = 1;
  uint16_t success = 0;
  while (count > 0)
  {
    count--;
    if (diag(address) == 0) success++;
  }
  return success;
}


int I2C_SCANNER::diag(uint8_t address)
{
  _wire->beginTransmission(address);
  int x = _wire->endTransmission();
  return x;
}


int32_t I2C_SCANNER::pingTime(uint8_t address)
{
  uint32_t start = micros();
  int x = diag(address);
  int32_t duration = (int32_t)(micros() - start);
  if (x == 0) return duration;
  return -duration;
}


uint8_t I2C_SCANNER::count(uint8_t start, uint8_t end)
{
  uint8_t cnt = 0;
  for (uint8_t addr = start; addr <= end; addr++)
  {
    if (diag(addr) == 0) cnt++;
  }
  return cnt;
}


bool I2C_SCANNER::setWireTimeout(uint32_t timeOut, bool reset)
{
  if (_timeout != timeOut)
  {
    _timeout = timeOut;
    //  not all platforms support this.
#if defined(WIRE_HAS_TIMEOUT)
    _wire->setWireTimeout(timeOut, reset);
#endif
    return true;
  }
  return false;
}


uint32_t I2C_SCANNER::getWireTimeout()
{
  return _timeout;
}


////////////////////////////////////////////////////
//
//  PRIVATE
//
int I2C_SCANNER::_init()
{
  Wire.begin();

  _wirePortCount = 1;
#if defined WIRE_IMPLEMENT_WIRE1 || WIRE_INTERFACES_COUNT > 1
  Wire1.begin();
  _wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE2 || WIRE_INTERFACES_COUNT > 2
  Wire2.begin();
  _wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE3 || WIRE_INTERFACES_COUNT > 3
  Wire3.begin();
  _wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE4 || WIRE_INTERFACES_COUNT > 4
  Wire4.begin();
  _wirePortCount++;
#endif
#if defined WIRE_IMPLEMENT_WIRE5 || WIRE_INTERFACES_COUNT > 5
  Wire5.begin();
  _wirePortCount++;
#endif
  return _wirePortCount;
}


uint32_t I2C_SCANNER::getDeviceID(uint8_t address)
{
  uint8_t DEVICEID_ADDRESS = 0x7C;
  _wire->beginTransmission(DEVICEID_ADDRESS);
  _wire->write(address << 1);             //  address shifted one bit
  int code = _wire->endTransmission(false);  //  explicit send a restart.
  if (code != 0)
  {
    return 0xFF000000 + code;
  }
  if (_wire->requestFrom(DEVICEID_ADDRESS, (uint8_t)3) != 3)
  {
    return 0xFFFFFFFF;
  }
  //  build up return code
  uint32_t deviceId = _wire->read();
  deviceId <<= 8;
  deviceId |= _wire->read();
  deviceId <<= 8;
  deviceId |= _wire->read();
  return deviceId;
}


//  -- END OF FILE --

