#include <RH_RN2483.h>

// RH_RH2483.cpp
//
// Copyright (C) 2024 Industrial Shields


RH_RN2483::RH_RN2483(HardwareSerial *s)
  :
  _s(s)
{

}

bool RH_RN2483::init()
{
  // When a message is available, Aux will go low 5 msec before the first character is output
  // So if we ever wait more than this period of time after Aux low, can conclude there will be no data
  _s->setTimeout(200);

  _mode = RH_RN2483_TX_MODE;

  if (!reset())
    return false; // Could not communicate with module or wrong type of module

  if (!getVersion())
    return false;

  if (!setCodingRate4(5))
    return false;
  // Serial.println("Coding Rate set");

  if (!setSignalBandwidth(125))
    return false;
  // Serial.println("Bandwidth set");

  if (!setPreambleLength(8))
    return false;
  // Serial.println("Preamble length set");

  if (!setSpreadingFactor(12))
    return false;
  // Serial.println("Spreading factor set");

  if (!setTxPower(13))
    return false;
  // Serial.println("Power set");

  if (!setFrequency(868000000))
    return false;
  // Serial.println("Frequency set");

  if (!setSyncWord(0x12))
    return false;
  // Serial.println("Sync Word set");

  if (!setWDT(0))
    return false;
  // Serial.println("WDT set to 0");

  // Send first a mac pause command, as the datasheet suggests
  uint8_t macpause[] = RH_RN2483_CMND_MACPAUSE;

  if (!_s->write(macpause, sizeof(macpause) - 1))
    return false;

  // Read response, if response is 0 exit
  if (readResponse("0\r\n", 12))
    return false;

  if (!setMode(RH_RN2483_RX_MODE))
    return false;

  return true;
}

uint8_t char2int(char val)
{
  return (val >= 'A' && val <= 'F') ? val - 'A' + 10 : val - '0';
}

void int2char(uint8_t val, uint8_t* dest)
{
  dest[0] = (val >> 4) < 10 ? (val >> 4) + '0' : (val >> 4) - 10 + 'A';
  dest[1] = (val & 0x0F) < 10 ? (val & 0x0F) + '0' : (val & 0x0F) - 10 + 'A';
}

bool RH_RN2483::setMode(uint8_t mode)
{
  if (mode == _mode)
    return true;

  else if (mode == RH_RN2483_RX_MODE)
  {
    // Set module on continuous rx mode
    uint8_t setrx[] = RH_RN2483_CMND_RADIO_RX;

    if (!_s->write(setrx, sizeof(setrx) - 1))
      return false;

    if (!_s->write("0", 1))
        return false;

    // Send end of command
    if (!_s->write(RH_RN2483_CMND_CRNL, 2))
      return false;

    uint8_t response[4];
    if (!_s->readBytes((char*)&response, sizeof(response)))
      return false;

    if (!strncmp((char*)&response, RH_RN2483_RESP_OK, 4) == 0)
      return false;

    _mode = mode;
    return true;
  }

  else if (mode == RH_RN2483_TX_MODE)
  {
    // Cancel RX Mode to be able to send
    uint8_t rxstop[] = RH_RN2483_CMND_RADIO_RXSTOP;
    if (!_s->write(rxstop, sizeof(rxstop) - 1))
      return false;

    uint8_t response[4];
    if (!_s->readBytes((char*)&response, sizeof(response)))
      return false;

    if (!strncmp((char*)&response, RH_RN2483_RESP_OK, 4) == 0)
      return false;

    _mode = mode;
    return true;
  }

  return false;
}

bool RH_RN2483::reset()
{

  if (_mode != RH_RN2483_TX_MODE)
    if (!setMode(RH_RN2483_TX_MODE))
      return false;

  uint8_t resetCommand[12] = RH_RN2483_CMND_SYS_RESET;
  uint8_t response[34];

  if (!_s->write(resetCommand, sizeof(resetCommand) - 1))
    return false;

  return _s->readBytes((char*)&response, sizeof(response));
}

bool RH_RN2483::getVersion()
{

  if (_mode != RH_RN2483_TX_MODE)
    if (!setMode(RH_RN2483_TX_MODE))
      return false;

  uint8_t versionCommand[14] = RH_RN2483_CMND_SYS_GET_VER;
  uint8_t response[36];

  if (!_s->write(versionCommand, sizeof(versionCommand) - 1))
    return false;

  if (!_s->readBytes((char*)&response, sizeof(response)))
    return false;

  return true;
}

// Check whether the latest received message is complete and uncorrupted
void RH_RN2483::validateRxBuf()
{
  size_t len = 0;
  for (len = _bufLen; _s->available(); len++)
    _buf[len] = _s->read();

  if (!strncmp((char*)(_buf + _bufLen), RH_RN2483_CMND_RADIO_RX_RESP, 10) == 0)
    return;

  // Ignore "radio_rx  "
  _bufLen =+ 10;

  // Extract the 4 headers
  _rxHeaderTo    = char2int(_buf[_bufLen++]) << 4 | char2int(_buf[_bufLen++]);
  _rxHeaderFrom  = char2int(_buf[_bufLen++]) << 4 | char2int(_buf[_bufLen++]);
  _rxHeaderId    = char2int(_buf[_bufLen++]) << 4 | char2int(_buf[_bufLen++]);
  _rxHeaderFlags = char2int(_buf[_bufLen++]) << 4 | char2int(_buf[_bufLen++]);

  if (_promiscuous || _rxHeaderTo == _thisAddress || _rxHeaderTo == RH_BROADCAST_ADDRESS)
  {
    // Avoid "radio_rx  FFFF0000" and "\r\n"
    _goodBufLen = len - _bufLen - 2;
    _rxGood++;
    _rxBufValid = true;
  }
}

void RH_RN2483::clearRxBuf()
{
  _rxBufValid = false;
  _bufLen = 0;
}

bool RH_RN2483::available()
{
  if (_rxBufValid)
    return true;

  if (!_s->available())
    return false;

  validateRxBuf();

  return _rxBufValid;
}

void RH_RN2483::printBuffer(uint8_t* buf, uint8_t len)
{
  for (int i = 0; i < len; i++)
    Serial.print((char)(char2int(buf[i++]) << 4 | char2int(buf[i])));
  Serial.println();
}

bool RH_RN2483::recv(uint8_t* buf, uint8_t* len)
{
  if (buf && len)
  {
    *len = _goodBufLen;
    memcpy(buf, _buf + _bufLen, *len);
  }
  clearRxBuf(); // This message accepted and cleared

  if (!setMode(RH_RN2483_RX_MODE))
    return false;

  return true;
}

bool RH_RN2483::send(const uint8_t* data, uint8_t len)
{
  if (len > RH_RN2483_MAX_MESSAGE_LEN)
    return false;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  ///////////////////////////////////////////////////////

  // Store data to send
  memcpy(_buf, data, len);

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_TX;
  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Header
  uint8_t header[2];

  int2char(_txHeaderTo, header);
  if (!_s->write(header, 2))
    return false;

  int2char(_txHeaderFrom, header);
  if (!_s->write(header, 2))
    return false;

  int2char(_txHeaderId, header);
  if (!_s->write(header, 2))
    return false;

  int2char(_txHeaderFlags, header);
  if (!_s->write(header, 2))
    return false;

  // Send TX message
  uint8_t tx_msg[2];
  for (int i = 0; i < len - 1; i++)
  {
    int2char(_buf[i], tx_msg);
    if (!_s->write(tx_msg, 2))
      return false;
  }

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response "ok\r\n"
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (!waitPacketSent())
    return false;

  _txGood++;

  // Set RX mode to be able to receive messages
  if (!setMode(RH_RN2483_RX_MODE))
    return false;

  return true;
}

bool RH_RN2483::readResponse(const char* resp, uint8_t len)
{
  uint8_t response[len];
  if (!_s->readBytes((char*)&response, len))
    return false;

  if (!strncmp((char*)&response, resp, len) == 0)
    return false;

  return true;
}

bool RH_RN2483::waitPacketSent()
{
  // Wait for transmission to finish
  while (!_s->available());

  // Read response "radio_tx_ok\r\n"
  if (!readResponse(RH_RN2483_RESP_RADIO_TX_OK, 13))
    return false;

  return true;
}

uint8_t RH_RN2483::maxMessageLength()
{
  return RH_RN2483_MAX_MESSAGE_LEN;
}

bool RH_RN2483::setTxPower(int8_t power)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  // Check if valid power
  if (power < -3 && power > 15)
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_PWR;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send pwr value
  uint8_t pwr[2];
  if (power < 0)
  {
    if (!_s->write("-", 1))
      return false;
    power = -power;
  }
  if (power < 10)
  {
    pwr[0] = power + '0';
    if (!_s->write(pwr, 1))
      return false;
  }
  else
  {
    itoa((int)power, (char*)pwr, 10);

    if (!_s->write(pwr, 2))
        return false;
  }

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setFrequency(int frequency)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  // Check if valid frequency
  if (!((frequency > 433050000 && frequency < 434790000) ||
      (frequency > 863000000 && frequency < 870000000)))
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_FREQ;
  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send freq value
  uint8_t freq[10];
  itoa(frequency, (char*)&freq, 10);

  if (!_s->write(freq, sizeof(freq) - 1))
    return false;

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setSpreadingFactor(uint8_t spreadingFactor)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  // Check if valid spreading factor
  if (spreadingFactor < 7 || spreadingFactor > 12)
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_SF;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send sf value
  uint8_t sf[2];
  if (spreadingFactor < 10)
  {
    sf[0] = spreadingFactor + '0';
    if (!_s->write(sf, 1))
      return false;
  }
  else
  {
    itoa(spreadingFactor, (char*)&sf, 10);

    if (!_s->write(sf, 2))
      return false;
  }

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setPreambleLength(uint16_t preambleLength)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  // Check if valid preamble length
  if (preambleLength < 0 || preambleLength > 65535)
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_PRLEN;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send prlen value
  uint8_t prlen[5];

  itoa(preambleLength, (char*)&prlen, 10);

  for (int i = 0; prlen[i] != '\0'; i++)
  {
    if (!_s->write((char*)&prlen[i], 1))
      return false;
  }

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setSignalBandwidth(uint16_t bandwidth)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
      return false;

  // Check if valid band width
  if (bandwidth != 125 && bandwidth != 250 && bandwidth != 500)
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_BW;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send prlen value
  uint8_t bw[3];

  itoa(bandwidth, (char*)&bw, 10);

  if (!_s->write(bw, 3))
    return false;

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setCodingRate4(uint8_t codingrate)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  // Check if valid cr
  if (codingrate < 5 || codingrate > 9 )
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_CR;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send cr value
  uint8_t cr[1];

  itoa(codingrate, (char*)&cr, 10);

  if (!_s->write(cr, 1))
    return false;

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setSyncWord(uint8_t syncword)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  if (syncword > 0xFF)
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_SYNC;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send sync word value
  uint8_t sync[2];
  int2char(syncword, sync);

  if (!_s->write(sync, 2))
    return false;

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}

bool RH_RN2483::setWDT(unsigned long wdt)
{

  uint8_t lastMode = _mode;

  if (!setMode(RH_RN2483_TX_MODE))
    return false;

  // Check if valid cr
  if (wdt < 0 || wdt > 4294967295 )
    return false;

  // Send command
  uint8_t config[] = RH_RN2483_CMND_RADIO_SET_WDT;

  if (!_s->write(config, sizeof(config) - 1))
    return false;

  // Send cr value
  uint8_t wdogt[10];

  itoa(wdt, (char*)&wdogt, 10);

  for (int i = 0; wdogt[i] != '\0'; i++)
  {
    if (!_s->write((char*)&wdogt[i], 1))
      return false;
  }

  // Send end of command
  if (!_s->write(RH_RN2483_CMND_CRNL, 2))
    return false;

  // Read response
  if (!readResponse(RH_RN2483_RESP_OK, 4))
    return false;

  if (lastMode != _mode)
  {
    if (!setMode(RH_RN2483_RX_MODE))
      return false;
  }

  return true;
}
