/**
 * PCF8591 Analog Port Expand
 * https://www.mischianti.org/2019/01/03/pcf8591-i2c-analog-i-o-expander/
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Renzo Mischianti www.mischianti.org All right reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "PCF8591.h"
#include "Wire.h"


/**
 * Constructor
 * @param address: i2c address
 */
PCF8591::PCF8591(uint8_t address){
	_wire = &Wire;

	_address = address;
};

#if !defined(__AVR) && !defined(ARDUINO_ARCH_SAMD) && !defined(TEENSYDUINO) && !defined(ARDUINO_ARCH_RENESAS)
	/**
	 * Constructor
	 * @param address: i2c address
	 * @param sda: sda pin
	 * @param scl: scl pin
	 */
PCF8591::PCF8591(uint8_t address, int sda, int scl){
		_wire = &Wire;

		_address = address;
		_sda = sda;
		_scl = scl;
	};

#endif

#if defined(ESP32) || defined(ARDUINO_ARCH_SAMD)|| defined(ARDUINO_ARCH_RP2040)  || defined(ARDUINO_ARCH_STM32) || defined(ARDUINO_ARCH_RENESAS)
	/**
	 * Constructor
	 * @param address: i2c address
	 */
	PCF8591::PCF8591(TwoWire *pWire, uint8_t address){
		_wire = pWire;

		_address = address;
	};

#endif
#if defined(ESP32)
	/**
	 * Constructor
	 * @param address: i2c address
	 * @param sda: sda pin
	 * @param scl: scl pin
	 */
	PCF8591::PCF8591(TwoWire *pWire, uint8_t address, int sda, int scl){
		_wire = pWire;

		_address = address;
		_sda = sda;
		_scl = scl;
	};

#endif




///**
// * Constructor
// * @param address: i2c address
// */
//PCF8591::PCF8591(uint8_t address){
//	_wire = &Wire;
//
//	_address = address;
//};
//#if !defined(__AVR) && !defined(__STM32F1__)
//	/**
//	 *
//	 * @param address: i2c address
//	 * @param sda: sda pin
//	 * @param scl: scl pin
//	 */
//	PCF8591::PCF8591(uint8_t address, uint8_t sda, uint8_t scl){
//		_wire = &Wire;
//
//		_address = address;
//		_sda = sda;
//		_scl = scl;
//	};
//
//	#ifdef ESP32
//		/**
//		 * Constructor
//		 * @param address: i2c address
//		 */
//		PCF8591::PCF8591(TwoWire *pWire, uint8_t address){
//			_wire = pWire;
//
//			_address = address;
//		};
//		/**
//		 *
//		 * @param address: i2c address
//		 * @param sda: sda pin
//		 * @param scl: scl pin
//		 */
//		PCF8591::PCF8591(TwoWire *pWire, uint8_t address, uint8_t sda, uint8_t scl){
//			_wire = pWire;
//
//			_address = address;
//			_sda = sda;
//			_scl = scl;
//		};
//
//	#endif
//
//#endif

void PCF8591::begin(uint8_t address){
	_address = address;
	return PCF8591::begin();
}

/**
 * wake up i2c controller
 */
void PCF8591::begin(){
#if !defined(__AVR)  && !defined(ARDUINO_ARCH_SAMD)  && !defined(TEENSYDUINO) && !defined(ARDUINO_ARCH_RENESAS)
	DEBUG_PRINT(F("begin(sda, scl) -> "));DEBUG_PRINT(_sda);DEBUG_PRINT(F(" "));DEBUG_PRINTLN(_scl);
//		_wire->begin(_sda, _scl);
#ifdef ARDUINO_ARCH_STM32
	_wire->begin((uint32_t)_sda, (uint32_t)_scl);
#elif defined(ARDUINO_ARCH_RP2040)
	_wire->setSCL(_scl);
	_wire->setSDA(_sda);
	_wire->begin();
#else
	_wire->begin((int)_sda, (int)_scl);
#endif
#else
//			Default pin for AVR some problem on software emulation
//			#define SCL_PIN _scl
// 			#define SDA_PIN _sda
	_wire->begin();
#endif
}

/**
 * Read all analog input in one trasmission
 * @param readType read datasheet for info
 * 	SINGLE_ENDED_INPUT
 *	TREE_DIFFERENTIAL_INPUT
 *	TWO_SINGLE_ONE_DIFFERENTIAL_INPUT
 * 	TWO_DIFFERENTIAL_INPUT
 * @return
 */
struct PCF8591::AnalogInput PCF8591::analogReadAll(byte readType){
	DEBUG_PRINTLN("Begin trasmission");
	_wire->beginTransmission(_address); // wake up PCF8591

	byte operation = AUTOINCREMENT_READ | readType | (_outputStatus&OUTPUT_MASK);

	DEBUG_PRINTLN("Write operation");
	_wire->write(operation); // control byte: reads ADC0 then auto-increment
	DEBUG_PRINTLN("End write (If code stop here add pullup resistor on SDA SCL)");
	_wire->endTransmission(); // end tranmission
	DEBUG_PRINTLN("Request response");
	_wire->requestFrom(_address, (uint8_t)5);

	/*uint8_t control =*/_wire->read();
	analogInput.ain0=_wire->read();
	analogInput.ain1=_wire->read();
	analogInput.ain2=_wire->read();
	analogInput.ain3=_wire->read();

	return analogInput;
};

/**
 * Read one specified channel
 * @param channel channel or analog identify (if readType is SINGLE_ENDED_INPUT)
 * @param readType read datasheet for info
 * 	SINGLE_ENDED_INPUT
 *	TREE_DIFFERENTIAL_INPUT
 *	TWO_SINGLE_ONE_DIFFERENTIAL_INPUT
 * 	TWO_DIFFERENTIAL_INPUT
 * @return
 */
uint8_t PCF8591::analogRead(uint8_t channel, byte readType){
	DEBUG_PRINTLN("Begin trasmission");
	_wire->beginTransmission(_address); // wake up PCF8591

	byte operation = channel | readType| (_outputStatus&OUTPUT_MASK);

	DEBUG_PRINTLN("Write operation");
	_wire->write(operation); // control byte: reads ADC0 then auto-increment
	DEBUG_PRINTLN("End write (If code stop here add pullup resistor on SDA SCL)");
	_wire->endTransmission(); // end tranmission
	DEBUG_PRINTLN("Request response");
	_wire->requestFrom(_address, (uint8_t)2);
	/*uint8_t control = */_wire->read();
	uint8_t ana=_wire->read();

	return ana;
};

/**
 * Read voltage of analog input
 * @param analogPin (Analog identifier)
 * @param microcontrollerReferenceVoltage get voltage from microcontroller voltage (only AVR no esp8266 for esp 3.3v fixed)
 * @param referenceVoltage if microcontrollerReferenceVoltage false take this value
 * @return
 */
float PCF8591::voltageRead(uint8_t analogPin, bool microcontrollerReferenceVoltage, float referenceVoltage){
	float voltageRef = referenceVoltage;
	if (microcontrollerReferenceVoltage) voltageRef = PCF8591::readVcc()/1000.0;
	float ana = PCF8591::analogRead(analogPin);
	return ana*voltageRef/255;
};
/**
 * To write votlage on output
 * @param value voltage to write
 * @param microcontrollerReferenceVoltage get voltage from microcontroller voltage (only AVR no esp8266 for esp 3.3v fixed)
 * @param referenceVoltage if microcontrollerReferenceVoltage false take this value
 */
void PCF8591::voltageWrite(float value, bool microcontrollerReferenceVoltage, float referenceVoltage){
	if (microcontrollerReferenceVoltage) referenceVoltage = PCF8591::readVcc()/1000.0;
	uint8_t ana = value*255/referenceVoltage;
	PCF8591::analogWrite(ana);
};

/**
 * Write value on output pin
 * @param value votlage in volts
 */
void PCF8591::analogWrite(uint8_t value){
	_outputStatus = ENABLE_OUTPUT;
	DEBUG_PRINTLN("Begin trasmission");
	_wire->beginTransmission(_address);
	DEBUG_PRINTLN("Write operation");
	_wire->write(ENABLE_OUTPUT); // sets the PCF8591 into a DA mode
	DEBUG_PRINTLN("Write value");
	_wire->write(value); // sets the output
	DEBUG_PRINTLN("End write (If code stop here add pullup resistor on SDA SCL)");
	_wire->endTransmission();
};

long PCF8591::readVcc(void) {
	#ifdef __AVR
		// Read 1.1V reference against AVcc
		// set the reference to Vcc and the measurement to the internal 1.1V reference
	#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
		ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
	#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
		ADMUX = _BV(MUX5) | _BV(MUX0);
	#else
		ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
	#endif

		delay(2); // Wait for Vref to settle
		ADCSRA |= _BV(ADSC); // Start conversion
		while (bit_is_set(ADCSRA, ADSC))
			; // measuring

		uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
		uint8_t high = ADCH; // unlocks both

		long result = (high << 8) | low;

		  result = 1083630L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
		  // scale_constant = internal1.1Ref * 1023 * 1000
		  // internal1.1Ref = 1.1 * Vcc1 (per voltmeter) / Vcc2 (per readVcc() function)
		return result; // Vcc in millivolts
	#else
//		float vdd = readVcc(); //  ESP.getVdd33(); //ESP.getVcc();
		return 3300;
	#endif
}

