/*
 *   DS2438.cpp
 *
 *   by Joe Bechter
 *
 *   (C) 2012, bechter.com
 *
 *   All files, software, schematics and designs are provided as-is with no warranty.
 *   All files, software, schematics and designs are for experimental/hobby use.
 *   Under no circumstances should any part be used for critical systems where safety,
 *   life or property depends upon it. You are responsible for all use.
 *   You are free to use, modify, derive or otherwise extend for your own non-commercial purposes provided
 *       1. No part of this software or design may be used to cause injury or death to humans or animals.
 *       2. Use is non-commercial.
 *       3. Credit is given to the author (i.e. portions © bechter.com), and provide a link to the original source.
 *
 */

#include "DS2438.h"

// DSROM FIELDS
#define DSROM_FAMILY    0
#define DSROM_CRC       7

#define DS2438MODEL     0x26

DS2438::DS2438(OneWire *ow, float currentShunt, int retryOnCRCError) {
    _ow = ow;
    _currentShunt = currentShunt;
    _retryOnCRCError = retryOnCRCError;
};

void DS2438::begin(){
    DeviceAddress searchDeviceAddress;

	_ow->reset_search();
    memset(searchDeviceAddress,0, 8);
    _temperature.clear();
    _voltageA.clear();
    _voltageB.clear();
    _error = true;
    _mode = (DS2438_MODE_CHA | DS2438_MODE_CHB | DS2438_MODE_TEMPERATURE);

	deviceFound = false; // Reset the number of devices when we enumerate wire devices

	while (_ow->search(searchDeviceAddress)) {
		if (validAddress(searchDeviceAddress)) {
			if (validFamily(searchDeviceAddress)) {
                memcpy(_address,searchDeviceAddress,8);
                DEFAULT_PAGE0(defaultConfig);
                writePage(0, defaultConfig);
                deviceFound = true;
			}
		}
	}
}

bool DS2438::isFound(){
    return deviceFound;
}

bool DS2438::validAddress(const uint8_t* deviceAddress) {
	return (_ow->crc8(deviceAddress, 7) == deviceAddress[DSROM_CRC]);
}

bool DS2438::validFamily(const uint8_t* deviceAddress) {
	switch (deviceAddress[DSROM_FAMILY]) {
	case DS2438MODEL:
		return true;
	default:
		return false;
	}
}

void DS2438::updateMultiple(){
    for(int i = 0;i< DS2438_MEDIAN_COUNT; i++){
        update(i==0);
        if(_error){
            return;
        }
        delay(DS2438_MEDIAN_DELAY);
    }
}

void DS2438::update(bool firstIteration) {
    uint8_t data[9];
    _error = true;
    
    if(!isFound()){
        return;
    }

    if (_mode & DS2438_MODE_CHA || _mode == DS2438_MODE_TEMPERATURE) {
        boolean doTemperature = _mode & DS2438_MODE_TEMPERATURE;
        if (!startConversion(DS2438_CHA, doTemperature)) {
            Serial.println("Error starting temp conversion ds2438 channel a");
            return;
        }
        if (!readPage(0, data)){
            
            Serial.println("Error reading zero page ds2438 channel a");
            return;
        }
            
        if (doTemperature) {
            _temperature.add((double)(((((int16_t)data[2]) << 8) | (data[1] & 0x0ff)) >> 3) * 0.03125);
        }
        if (_mode & DS2438_MODE_CHA) {
            _voltageA.add((((data[4] << 8) & 0x00300) | (data[3] & 0x0ff)) / 100.0);
        }
    }
    if (_mode & DS2438_MODE_CHB) {
        boolean doTemperature = _mode & DS2438_MODE_TEMPERATURE && !(_mode & DS2438_MODE_CHA);
        if (!startConversion(DS2438_CHB, doTemperature)) {
            Serial.println("Error starting temp conversion channel b ds2438");
            return;
        }
        if (!readPage(0, data)){
            Serial.println("Error reading zero page ds2438 channel b");
            return;
        }
        if (doTemperature) {
            int16_t upperByte = ((int16_t)data[2]) << 8;
            int16_t lowerByte = data[1] >> 3;
            int16_t fullByte = (upperByte | lowerByte);
            _temperature.add(((double)fullByte) * 0.03125);
        }
        _voltageB.add((((data[4] << 8) & 0x00300) | (data[3] & 0x0ff)) / 100.0);
    }
    
    int16_t upperByte = ((int16_t)data[6]) << 8;
    int16_t lowerByte = data[5];
    int16_t fullByte = (int16_t)(upperByte | lowerByte);
    float fullByteb = fullByte;
    _current.add((fullByteb) / ((4096.0f * _currentShunt)));


    if(firstIteration){
        if (readPage(1, data)){
            PageOne_t *pOne = (PageOne_t *) data;
            _ICA = pOne->ICA;
        }

        if (readPage(7, data)){
            PageSeven_t *pSeven = (PageSeven_t *) data;
            _CCA = pSeven->CCA0 | ((int16_t) pSeven->CCA1) << 8;
            _DCA = pSeven->DCA0 | ((int16_t) pSeven->DCA1) << 8;
        }
    }
    _error = false;
}

double DS2438::getTemperature() {
    return _temperature.getMedian();
}

float DS2438::getAh(){
    return _ICA / (2048.0f * _currentShunt);
}

long DS2438::getICA(){
    return _ICA;
}

long DS2438::getDCA(){
    return _DCA;
}

long DS2438::getCCA(){
    return _CCA;
}


float DS2438::getVoltage(int channel) {
    if (channel == DS2438_CHA) {
        return _voltageA.getMedian();
    } else if (channel == DS2438_CHB) {
        return _voltageB.getMedian();
    } else {
        return 0.0;
    }
}

float DS2438::getCurrent() {
    return _current.getMedian();
}

boolean DS2438::isError() {
    return _error;
}

boolean DS2438::startConversion(int channel, boolean doTemperature) {
    if(!isFound()){
        return false;
    }
    if (!selectChannel(channel)){
        return false;
    }
    _ow->reset();
    _ow->select(_address);
    if (doTemperature) {
        _ow->write(DS2438_TEMPERATURE_CONVERSION_COMMAND, 0);
        delay(DS2438_TEMPERATURE_DELAY);
        _ow->reset();
        _ow->select(_address);
    }
    _ow->write(DS2438_VOLTAGE_CONVERSION_COMMAND, 0);
    delay(DS2438_VOLTAGE_CONVERSION_DELAY);
    return true;
}

boolean DS2438::selectChannel(int channel) {
    if(!isFound()){
        return false;
    }
    uint8_t data[9];
    if (readPage(0, data)) {
        if (channel == DS2438_CHB){
            data[0] = data[0] | 0x08;
        }
        else {
            data[0] = data[0] & 0xf7;
        }
        writePage(0, data);
        return true;
    }
    Serial.println("Could not read page zero data");
    return false;
}

void DS2438::writePage(int page, uint8_t *data) {
    _ow->reset();
    _ow->select(_address);
    _ow->write(DS2438_WRITE_SCRATCHPAD_COMMAND, 0);
    if ((page >= PAGE_MIN) && (page <= PAGE_MAX)) {
        _ow->write(page, 0);
    } else {
        return;
    }
    for (int i = 0; i < 8; i++){
        _ow->write(data[i], 0);
    }
    _ow->reset();
    _ow->select(_address);
    _ow->write(DS2438_COPY_SCRATCHPAD_COMMAND, 0);
    _ow->write(page, 0);
}

boolean DS2438::readPage(int page, uint8_t *data) {
    bool valid = false;
    for(int retry = 0;retry < this->_retryOnCRCError && !valid; retry ++){
        //TODO if all data is 0 0 is a valid crc, but most likly not as intended
        _ow->reset();
        _ow->select(_address);
        _ow->write(DS2438_RECALL_MEMORY_COMMAND, 0);
        if ((page >= PAGE_MIN) && (page <= PAGE_MAX)) {
            _ow->write(page, 0);
        } else {
            return false;
        }
        _ow->reset();
        _ow->select(_address);
        _ow->write(DS2438_READ_SCRATCHPAD_COMMAND, 0);
        _ow->write(page, 0);
        for (int i = 0; i < 9; i++){
            data[i] = _ow->read();
        }
        valid = _ow->crc8(data, 8) == data[8];
    }
    return valid;
}

