#include "RADIO_rda5807m.h"
#include "projectprefix.h"
#include <arduino.h>
#include <Wire.h>


// - - - - -
// RADIO - A library to control the RDA5807M radio chip
// RADIO.cpp: Library implementation
//
// Copyright (c) 2014 by Matthias Hertel, http://www.mathertel.de
// This work is licensed under a BSD style license. See http://www.mathertel.de/License.aspx
//
// This library enables the use of the radio chip RDA5807M from http://www.rdamicro.com/.
//
// Documentation and source code is available at http://www.mathertel.de/Arduino

///
/// \file Radio.cpp
/// \brief Library implementation for the radio libraries to control radio chips.
///
/// \author Matthias Hertel, http://www.mathertel.de
/// \copyright Copyright (c) 2014 by Matthias Hertel.\n
/// This work is licensed under a BSD style license.\n
/// See http://www.mathertel.de/License.aspx
///
/// \details
/// This library enables the use of diverse radio chips by sharing the same class definition.
/// Implementation for the following Radio Chips are available:
/// * RDA5807M
/// * SI4703
///
/// The following chip is planned to be supported too:
/// * TEA5767
///
/// More documentation and source code is available at http://www.mathertel.de/Arduino
///
/// History:
/// --------
/// * 08.07.2014 creation of the common radio class.
/// * 15.07.2014 examples working with RDA5807M.
/// * 26.08.2014 examples working with SI4703.
/// * 31.08.2014 Doxygen style comments added.



#pragma mark - Register Definitions
// this chip only supports FM mode
#define FREQ_STEPS 10

#define RADIO_REG_CHIPID  0x00

#define RADIO_REG_CTRL    0x02
#define RADIO_REG_CTRL_OUTPUT 0x8000
#define RADIO_REG_CTRL_UNMUTE 0x4000
#define RADIO_REG_CTRL_MONO   0x2000
#define RADIO_REG_CTRL_BASS   0x1000
#define RADIO_REG_CTRL_SEEKUP 0x0200
#define RADIO_REG_CTRL_SEEK   0x0100
#define RADIO_REG_CTRL_RDS    0x0008
#define RADIO_REG_CTRL_NEW    0x0004
#define RADIO_REG_CTRL_RESET  0x0002
#define RADIO_REG_CTRL_ENABLE 0x0001

#define RADIO_REG_CHAN    0x03
#define RADIO_REG_CHAN_SPACE     0x0003
#define RADIO_REG_CHAN_SPACE_100 0x0000
#define RADIO_REG_CHAN_BAND      0x000C
#define RADIO_REG_CHAN_BAND_FM      0x0000
#define RADIO_REG_CHAN_BAND_FMWORLD 0x0008
#define RADIO_REG_CHAN_TUNE   0x0010
//      RADIO_REG_CHAN_TEST   0x0020
#define RADIO_REG_CHAN_NR     0x7FC0

#define RADIO_REG_R4    0x04
#define RADIO_REG_R4_EM50   0x0800
//      RADIO_REG_R4_RES   0x0400
#define RADIO_REG_R4_SOFTMUTE   0x0200
#define RADIO_REG_R4_AFC   0x0100


#define RADIO_REG_VOL     0x05
#define RADIO_REG_VOL_VOL   0x000F


#define RADIO_REG_RA      0x0A
#define RADIO_REG_RA_RDS       0x8000
#define RADIO_REG_RA_RDSBLOCK  0x0800
#define RADIO_REG_RA_STEREO    0x0400
#define RADIO_REG_RA_NR        0x03FF

#define RADIO_REG_RB          0x0B
#define RADIO_REG_RB_FMTRUE   0x0100
#define RADIO_REG_RB_FMREADY  0x0080


#define RADIO_REG_RDS0	0x0C
#define RADIO_REG_RDSA	0x0C
#define RADIO_REG_RDSB	0x0D
#define RADIO_REG_RDSC	0x0E
#define RADIO_REG_RDSD	0x0F

// I2C-Address RDA Chip for sequential  Access
#define I2C_SEQ  0x10

// I2C-Address RDA Chip for Index  Access
#define I2C_INDX  0x11

#pragma mark - Implementation

/// Setup the radio object and initialize private variables to 0.
/// Don't change the radio chip (yet).
RADIO_RDA5807M::RADIO_RDA5807M() {
  memset(this, 0, sizeof(RADIO_RDA5807M));
  MAXVOLUME = 15;
} // RADIO_RDA5807M()


// initialize all internals.
bool RADIO_RDA5807M::init() {
  bool result = false; // no chip found yet.
  DEBUG_FUNC0("init");

  Wire.begin();
  Wire.beginTransmission(I2C_INDX);
  result = Wire.endTransmission();
  if (result == 0) {
    DEBUG_STR("radio found.");
    result = true;

    // initialize all registers
    registers[RADIO_REG_CHIPID] = 0x5804;  // 00 id
    registers[1] = 0x0000;  // 01 not used
    registers[RADIO_REG_CTRL] = (RADIO_REG_CTRL_RESET | RADIO_REG_CTRL_ENABLE);
    setBand(RADIO_BAND_FM);
    registers[RADIO_REG_R4] = RADIO_REG_R4_EM50;//  0x1800;  // 04 DE ? SOFTMUTE
    registers[RADIO_REG_VOL] = 0x9081; // 0x81D1;  // 0x82D1 / INT_MODE, SEEKTH=0110,????, Volume=1
    registers[6] = 0x0000;
    registers[7] = 0x0000;
    registers[8] = 0x0000;
    registers[9] = 0x0000;

    // reset the chip
    _saveRegisters();

    registers[RADIO_REG_CTRL] = RADIO_REG_CTRL_ENABLE;
    _saveRegister(RADIO_REG_CTRL);
  }  // if
  return(result);
} // init()


/// switch the power off
void RADIO_RDA5807M::term() {
  DEBUG_FUNC0("term");
  setVolume(0);
  registers[RADIO_REG_CTRL] = 0x0000;   // all bits off
  _saveRegisters();
} // term()


// ----- Volume control -----

void RADIO_RDA5807M::setVolume(uint8_t newVolume) {
  _volume = newVolume;
  newVolume &= RADIO_REG_VOL_VOL;
  registers[RADIO_REG_VOL] &= (~RADIO_REG_VOL_VOL);
  registers[RADIO_REG_VOL] |= newVolume;
  _saveRegister(RADIO_REG_VOL);
} // setVolume()


uint8_t RADIO_RDA5807M::getVolume() {
  return(_volume);
} // getVolume()


// ----- bass boost control -----

void RADIO_RDA5807M::setBassBoost(bool switchOn) {
  _bassBoost = switchOn;
  uint16_t regCtrl = registers[RADIO_REG_CTRL];
  if (switchOn)
  regCtrl |= RADIO_REG_CTRL_BASS;
  else
  regCtrl &= (~RADIO_REG_CTRL_BASS);
  registers[RADIO_REG_CTRL] = regCtrl;
  _saveRegister(RADIO_REG_CTRL);
} // setBassBoost()


bool RADIO_RDA5807M::getBassBoost() {
  return(_bassBoost);
} // getBassBoost()


// ----- mono control -----

void RADIO_RDA5807M::setMono(bool switchOn) {
  _mono = switchOn;

  registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK);
  if (switchOn) {
    registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_MONO;
  }
  else {
    registers[RADIO_REG_CTRL] &= ~RADIO_REG_CTRL_MONO;
  }
  _saveRegister(RADIO_REG_CTRL);
} // setMono()


bool RADIO_RDA5807M::getMono() {
  return(_mono);
} // getMono()


// ----- mute control -----

void RADIO_RDA5807M::setMute(bool switchOn) {
  _mute = switchOn;

  if (switchOn) {
    // now don't unmute
    registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_UNMUTE);
  }
  else {
    // now unmute
    registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_UNMUTE;
  } // if
  _saveRegister(RADIO_REG_CTRL);
} // setMute()


bool RADIO_RDA5807M::getMute() {
  return(_mute);
} // getMute()


// ----- softmute control -----

void RADIO_RDA5807M::setSoftMute(bool switchOn) {
  _softMute = switchOn;

  if (switchOn) {
    registers[RADIO_REG_R4] |= (RADIO_REG_R4_SOFTMUTE);
  }
  else {
    registers[RADIO_REG_R4] &= (~RADIO_REG_R4_SOFTMUTE);
  } // if
  _saveRegister(RADIO_REG_R4);
} // setSoftMute()


bool RADIO_RDA5807M::getSoftMute() {
  return(_softMute);
} // getSoftMute()


// ----- receiver control -----

// some implementations to return internal variables if used by concrete chip implementations

void RADIO_RDA5807M::setBand(RADIO_BAND newBand) {
  RADIO_FREQ l, h, s;

  _band = newBand;
  if (newBand == RADIO_BAND_FM) {
    l = 8700;
    h = 10800;
    s = 10;

  }
  else if (newBand == RADIO_BAND_FMWORLD) {
    l = 7600;
    h = 10800;
    s = 10;
  } // if

  _freqHigh = h;
  _freqLow = l;
  _freqSteps = s;

  uint16_t r;
  if (newBand == RADIO_BAND_FM) { r = RADIO_REG_CHAN_BAND_FM; }
  else if (newBand == RADIO_BAND_FMWORLD) { r = RADIO_REG_CHAN_BAND_FMWORLD; }
  registers[RADIO_REG_CHAN] = (r | RADIO_REG_CHAN_SPACE_100);
} // setBand()


// retrieve the real frequency from the chip after automatic tuning.
RADIO_FREQ RADIO_RDA5807M::getFrequency() {
  // check register A
  Wire.requestFrom (I2C_SEQ, 2);
  registers[RADIO_REG_RA] = _read16();
  Wire.endTransmission();

  uint16_t ch = registers[RADIO_REG_RA] & RADIO_REG_RA_NR;

  _freq = _freqLow + (ch * 10);  // assume 100 kHz spacing
  return (_freq);
}  // getFrequency


void RADIO_RDA5807M::setFrequency(RADIO_FREQ newF) {
  DEBUG_FUNC1("setFrequency", newF);
  uint16_t newChannel;
  uint16_t regChannel = registers[RADIO_REG_CHAN] & (RADIO_REG_CHAN_SPACE | RADIO_REG_CHAN_BAND);

  if (newF < _freqLow) newF = _freqLow;
  if (newF > _freqHigh) newF = _freqHigh;
  newChannel = (newF - _freqLow) / 10;

  regChannel += RADIO_REG_CHAN_TUNE; // enable tuning
  regChannel |= newChannel << 6;

  // enable output and unmute
  registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_OUTPUT | RADIO_REG_CTRL_UNMUTE | RADIO_REG_CTRL_RDS | RADIO_REG_CTRL_ENABLE; //  | RADIO_REG_CTRL_NEW
  _saveRegister(RADIO_REG_CTRL);

  registers[RADIO_REG_CHAN] = regChannel;
  _saveRegister(RADIO_REG_CHAN);

  // adjust Volume
  _saveRegister(RADIO_REG_VOL);
} // setFrequency()


void RADIO_RDA5807M::setBandFrequency(RADIO_BAND newBand, RADIO_FREQ newFreq) {
  setBand(newBand);
  setFrequency(newFreq);
} // setBandFrequency()


// start seek mode upwards
void RADIO_RDA5807M::seekUp(bool toNextSender) {
  // start seek mode
  registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_SEEKUP;
  registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_SEEK;
  _saveRegister(RADIO_REG_CTRL);

  if (! toNextSender) {
    // stop scanning right now
    registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK);
    _saveRegister(RADIO_REG_CTRL);
  } // if
} // seekUp()


// start seek mode downwards
void RADIO_RDA5807M::seekDown(bool toNextSender) {
  registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEKUP);
  registers[RADIO_REG_CTRL] |= RADIO_REG_CTRL_SEEK;
  _saveRegister(RADIO_REG_CTRL);

  if (! toNextSender) {
    // stop scanning right now
    registers[RADIO_REG_CTRL] &= (~RADIO_REG_CTRL_SEEK);
    _saveRegister(RADIO_REG_CTRL);
  } // if
} // seekDown()


RADIO_BAND RADIO_RDA5807M::getBand()         { return(_band); }
RADIO_FREQ RADIO_RDA5807M::getMinFrequency() { return(_freqLow); }
RADIO_FREQ RADIO_RDA5807M::getMaxFrequency() { return(_freqHigh); }
RADIO_FREQ RADIO_RDA5807M::getFrequencyStep(){ return(_freqSteps); }


void RADIO_RDA5807M::getRadioInfo(RADIO_INFO *info) {
  // set everything to false and 0.
  memset(info, 0, sizeof(RADIO_INFO));
  // read data from registers A .. F of the chip into class memory
  _readRegisters();
  info->active = true; // ???
  if (registers[RADIO_REG_RA] & RADIO_REG_RA_STEREO) info->stereo = true;
  if (registers[RADIO_REG_RA] & RADIO_REG_RA_RDS) info->rds = true;
  info->rssi = registers[RADIO_REG_RB] >> 10;
  if (registers[RADIO_REG_RB] & RADIO_REG_RB_FMTRUE) info->tuned = true;
  if (registers[RADIO_REG_CTRL] & RADIO_REG_CTRL_MONO) info->mono = true;
}


/// Return current settings as far as no chip is required.
/// When using the radio::setXXX methods, no chip specific implementation is needed.
void RADIO_RDA5807M::getAudioInfo(AUDIO_INFO *info) {
  // set everything to false and 0.
  memset(info, 0, sizeof(AUDIO_INFO));
  info->volume = _volume;
  info->mute = _mute;
  info->softmute = _softMute;
  info->bassBoost = _bassBoost;
}


// check if RDS Data is available and good
void RADIO_RDA5807M::checkRDS() {
  // DEBUG_FUNC0("checkRDS");

  // check RDS data if there is a listener !
	if (!_sendRDS) {
		return;
	}



    // check register A
    Wire.requestFrom (I2C_SEQ, 2);
    registers[RADIO_REG_RA] = _read16();
    Wire.endTransmission();

    if (registers[RADIO_REG_RA] & RADIO_REG_RA_RDSBLOCK) {
      DEBUG_STR("BLOCK_E found.");
    } // if

	if (registers[RADIO_REG_RA] & RADIO_REG_RA_RDS) {
		// check for new RDS data available
		bool result = false;

		Wire.beginTransmission(I2C_INDX);                // Device 0x11 for random access
		Wire.write(RADIO_REG_RDSA);                   // Start at Register 0x0C
		Wire.endTransmission(0);                         // restart condition

		// Retransmit device address with READ, followed by 4 words
		Wire.requestFrom(I2C_INDX, 8, 1);
		for (short i = RADIO_REG_RDS0; i < (4 + RADIO_REG_RDS0); ++i) {
			const uint16_t newData = _read16();
			if (newData != registers[i]) {
    	  		registers[i] = newData;
    	  		result = true;
			}
		}
		Wire.endTransmission();
		// _printHex(registers[RADIO_REG_RDSA]); _printHex(registers[RADIO_REG_RDSB]);
		// _printHex(registers[RADIO_REG_RDSC]); _printHex(registers[RADIO_REG_RDSD]);
		// Serial.println();

		if (result) {
		// new data in the registers
		// send to RDS decoder
			_sendRDS(&registers[RADIO_REG_RDS0]);
		}
	}
}

const uint16_t * RADIO_RDA5807M::getRDSBlocks() {
	return &registers[RADIO_REG_RDS0];
}

// send valid and good data to the RDS processor via newFunction
// remember the RDS function
void RADIO_RDA5807M::attachReceiveRDS(receiveRDSFunction newFunction)
{
	LOGTHIS;
	_sendRDS = newFunction;
} // attachReceiveRDS()


// format the current frequency for display and printing
void RADIO_RDA5807M::formatFrequency(char *s, uint8_t length) {
	RADIO_BAND b = getBand();
	RADIO_FREQ f = getFrequency();

  if ((s != NULL) && (length > 10)) {
    *s = '\0';

    if ((b == RADIO_BAND_FM) || (b == RADIO_BAND_FMWORLD)) {
      // " ff.ff MHz" or "fff.ff MHz"
      int16_to_s(s, (uint16_t)f);

      // insert decimal point
      s[5] = s[4]; s[4] = s[3]; s[3] = '.';

      // append units
      strcpy(s+6, " MHz");
    } // if

    //     f = _freqLow + (channel * _bandSteps);
    //     if (f < 10000) Serial.write(' ');
    //     Serial.print(f / 100); Serial.print('.'); Serial.print(f % 100); Serial.print(" MHz ");
  } // if

} // formatFrequency()


/// This is a special format routine used to format frequencies as strings with leading blanks.
/// up to 5 digits only ("    0".."99999")
/// *s MUST be able to hold the characters
void RADIO_RDA5807M::int16_to_s(char *s, uint16_t val) {
  uint8_t n = 5;

  while (n > 0) {
    n--;
    if ((n == 4) || (val > 0)) {
      s[n] = '0' + (val % 10);
      val = val / 10;
    }
    else {
      s[n] = ' ';
    }
  } // while
} // int16_to_s()


/// Prints a register as 4 character hexadecimal code with leading zeros.
void RADIO_RDA5807M::_printHex4(uint16_t val)
{
  if (val <= 0x000F) Serial.print('0');     // if less 2 Digit
  if (val <= 0x00FF) Serial.print('0');     // if less 3 Digit
  if (val <= 0x0FFF) Serial.print('0');     // if less 4 Digit
  Serial.print(val, HEX);
  Serial.print(' ');
} // _printHex4

// Load all status registers from to the chip
// registers 0A through 0F
// using the sequential read access mode.
void RADIO_RDA5807M::_readRegisters()
{
  Wire.requestFrom (I2C_SEQ, (6 * 2) );
  for (int i = 0; i < 6; i++) {
    registers[0xA+i] = _read16();
  }
  Wire.endTransmission();
}


// Save writable registers back to the chip
// The registers 02 through 06, containing the configuration
// using the sequential write access mode.
void RADIO_RDA5807M::_saveRegisters()
{
  DEBUG_FUNC0("-saveRegisters");
  Wire.beginTransmission(I2C_SEQ);
  for (int i = 2; i <= 6; i++)
  _write16(registers[i]);
  Wire.endTransmission();
} // _saveRegisters


// Save one register back to the chip
void RADIO_RDA5807M::_saveRegister(byte regNr)
{
  DEBUG_FUNC2X("-_saveRegister", regNr, registers[regNr]);

  Wire.beginTransmission(I2C_INDX);
  Wire.write(regNr);
  _write16(registers[regNr]);
  Wire.endTransmission();
} // _saveRegister


// write a register value using 2 bytes into the Wire.
void RADIO_RDA5807M::_write16(uint16_t val)
{
  Wire.write(val >> 8); Wire.write(val & 0xFF);
} // _write16


// read a register value using 2 bytes in a row
uint16_t RADIO_RDA5807M::_read16(void)
{
  uint8_t hiByte = Wire.read();
  uint8_t loByte = Wire.read();
  return(256*hiByte + loByte);
} // _read16


#pragma mark - Debug stuff
// enable debugging information on Serial port.
void RADIO_RDA5807M::debugEnable(bool enable) {
  _debugEnabled = enable;
} // debugEnable()


// print out all radio information
void RADIO_RDA5807M::debugRadioInfo() {
  RADIO_INFO info;
  this->getRadioInfo(&info);

  Serial.print(info.rds    ? " RDS"    : " ---");
  Serial.print(info.tuned  ? " TUNED"  : " -----");
  Serial.print(info.stereo ? " STEREO" : "  MONO ");
  Serial.print("  RSSI: "); Serial.print(info.rssi);
  Serial.println();
} // debugRadioInfo()


// print out all audio information
void RADIO_RDA5807M::debugAudioInfo() {
  AUDIO_INFO info;
  this->getAudioInfo(&info);

  Serial.print(info.bassBoost ? " BASS"  : " ----");
  Serial.print(info.mute      ? " MUTE"  : " ----");
  Serial.print(info.softmute  ? " SOFTMUTE"  : " --------");
  Serial.println();
} // debugAudioInfo()


// send a status report to the serial port
// dump all registers to Serial output
void RADIO_RDA5807M::debugStatus() {
  char s[12];

  // read data from registers A .. F of the chip into class memory
  _readRegisters();

  formatFrequency(s, sizeof(s));
  Serial.print("Frequency="); Serial.print(s);

  uint16_t pi = registers[RADIO_REG_RDSA];
  Serial.print(" PI="); _printHex4(pi);

  Serial.print((registers[RADIO_REG_RA] & RADIO_REG_RA_STEREO) ? " Stereo" : " Mono  ");
  Serial.print((registers[RADIO_REG_RA] & RADIO_REG_RA_RDS)    ? " ---"    : " RDS");

  int rssi = registers[RADIO_REG_RB] >> 10;

  Serial.print(" Sig="); if (rssi < 10) Serial.write(' ');
  Serial.print(rssi);
  Serial.print(' ');
  for(int i = 0;i < rssi - 15;i++) {Serial.write('*');} // Empfangspegel ab 15. Zeichen
  Serial.println();

  // ruler
  Serial.println("0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F");
  // variables
  for (int n = 0; n < 16; n++) { _printHex4(registers[n]); }
  Serial.println();

  // registers
  Wire.beginTransmission(I2C_INDX);                // Device 0x11 for random access
  Wire.write(0x00);                   // Start at Register 0x0C
  Wire.endTransmission(0);                         // restart condition
  Wire.requestFrom(I2C_INDX,32,1);                  // Retransmit device address with READ, followed by 8 bytes
  for (int n = 0; n < 16; n++) {
    _printHex4(_read16());
  }
  Wire.endTransmission();
  Serial.println();

  // clear text information in Registers
  if (getBassBoost()) Serial.print("BassBoost ");
  if (getMono()) Serial.print("Mono ");
  int v = getVolume();
  Serial.print("Volume="); Serial.print(v); Serial.print(' ');
  Serial.println();
} // debugStatus

void RADIO_RDA5807M::debugScan()
{
  DEBUG_FUNC0("debugScan");
  uint16_t regChannel = registers[RADIO_REG_CHAN] & (RADIO_REG_CHAN_SPACE | RADIO_REG_CHAN_BAND);
  RADIO_FREQ f = _freqLow;
  int channel = 0;

  while (f < _freqHigh) {
    registers[RADIO_REG_CHAN] = regChannel | RADIO_REG_CHAN_TUNE | (channel << 6);
    _saveRegister(RADIO_REG_CHAN);

    delay(500);
    debugStatus();

    f += _freqSteps;
    channel += 1;
  } // while
} // debugScan



// The End.


