// -----------------------------------------------------------------------------
// V9261F based power monitor
// Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------

#if SENSOR_SUPPORT && V9261F_SUPPORT

#pragma once

#include "Arduino.h"
#include "BaseSensor.h"
extern "C" {
    #include "libs/fs_math.h"
}

#include <SoftwareSerial.h>

class V9261FSensor : public BaseSensor {

    public:

        // ---------------------------------------------------------------------
        // Public
        // ---------------------------------------------------------------------

        V9261FSensor(): BaseSensor(), _data() {
            _count = 6;
            _sensor_id = SENSOR_V9261F_ID;
        }

        ~V9261FSensor() {
            if (_serial) delete _serial;
        }

        // ---------------------------------------------------------------------

        void setRX(unsigned char pin_rx) {
            if (_pin_rx == pin_rx) return;
            _pin_rx = pin_rx;
            _dirty = true;
        }

        void setInverted(bool inverted) {
            if (_inverted == inverted) return;
            _inverted = inverted;
            _dirty = true;
        }

        // ---------------------------------------------------------------------

        unsigned char getRX() {
            return _pin_rx;
        }

        bool getInverted() {
            return _inverted;
        }

        // ---------------------------------------------------------------------
        // Sensor API
        // ---------------------------------------------------------------------

        // Initialization method, must be idempotent
        void begin() {

            if (!_dirty) return;

            if (_serial) delete _serial;

            _serial = new SoftwareSerial(_pin_rx, SW_SERIAL_UNUSED_PIN, _inverted, 32);
            _serial->enableIntTx(false);
            _serial->begin(V9261F_BAUDRATE);

            _ready = true;
            _dirty = false;

        }

        // Descriptive name of the sensor
        String description() {
            char buffer[28];
            snprintf(buffer, sizeof(buffer), "V9261F @ SwSerial(%u,NULL)", _pin_rx);
            return String(buffer);
        }

        // Descriptive name of the slot # index
        String slot(unsigned char index) {
            return description();
        };

        // Address of the sensor (it could be the GPIO or I2C address)
        String address(unsigned char index) {
            return String(_pin_rx);
        }

        // Loop-like method, call it in your main loop
        void tick() {
            _read();
        }

        // Type for slot # index
        unsigned char type(unsigned char index) {
            if (index == 0) return MAGNITUDE_CURRENT;
            if (index == 1) return MAGNITUDE_VOLTAGE;
            if (index == 2) return MAGNITUDE_POWER_ACTIVE;
            if (index == 3) return MAGNITUDE_POWER_REACTIVE;
            if (index == 4) return MAGNITUDE_POWER_APPARENT;
            if (index == 5) return MAGNITUDE_POWER_FACTOR;
            return MAGNITUDE_NONE;
        }

        // Current value for slot # index
        double value(unsigned char index) {
            if (index == 0) return _current;
            if (index == 1) return _voltage;
            if (index == 2) return _active;
            if (index == 3) return _reactive;
            if (index == 4) return _apparent;
            if (index == 5) return _apparent > 0 ? 100 * _active / _apparent : 100;
            return 0;
        }

    protected:

        // ---------------------------------------------------------------------
        // Protected
        // ---------------------------------------------------------------------

        void _read() {

            static unsigned char state = 0;
            static unsigned long last = 0;
            static bool found = false;
            static unsigned char index = 0;

            if (state == 0) {

                while (_serial->available()) {
                    _serial->flush();
                    found = true;
                    last = millis();
                }

                if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
                    _serial->flush();
                    index = 0;
                    state = 1;
                }

            } else if (state == 1) {

                while (_serial->available()) {
                    _serial->read();
                    if (index++ >= 7) {
                        _serial->flush();
                        index = 0;
                        state = 2;
                    }
                }

            } else if (state == 2) {

                while (_serial->available()) {
                    _data[index] = _serial->read();
                    if (index++ >= 19) {
                        _serial->flush();
                        last = millis();
                        state = 3;
                    }
                }

            } else if (state == 3) {

                if (_checksum()) {

                    _active = (double) (
                        (_data[3]) +
                        (_data[4] << 8) +
                        (_data[5] << 16) +
                        (_data[6] << 24)
                    ) / _ratioP;

                    _reactive = (double) (
                        (_data[7]) +
                        (_data[8] <<  8) +
                        (_data[9] << 16) +
                        (_data[10] << 24)
                    ) / _ratioR;

                    _voltage = (double) (
                        (_data[11]) +
                        (_data[12] <<  8) +
                        (_data[13] << 16) +
                        (_data[14] << 24)
                    ) / _ratioV;

                    _current = (double) (
                        (_data[15]) +
                        (_data[16] <<  8) +
                        (_data[17] << 16) +
                        (_data[18] << 24)
                    ) / _ratioC;

                    if (_active < 0) _active = 0;
                    if (_reactive < 0) _reactive = 0;
                    if (_voltage < 0) _voltage = 0;
                    if (_current < 0) _current = 0;

                    _apparent = fs_sqrt(_reactive * _reactive + _active * _active);

                }

                last = millis();
                index = 0;
                state = 4;

            } else if (state == 4) {

                while (_serial->available()) {
                    _serial->flush();
                    last = millis();
                }

                if (millis() - last > V9261F_SYNC_INTERVAL) {
                    state = 1;
                }

            }

        }

        bool _checksum() {
            unsigned char checksum = 0;
            for (unsigned char i = 0; i < 19; i++) {
                checksum = checksum + _data[i];
            }
            checksum = ~checksum + 0x33;
            return checksum == _data[19];
        }

        // ---------------------------------------------------------------------

        unsigned int _pin_rx = V9261F_PIN;
        bool _inverted = V9261F_PIN_INVERSE;
        SoftwareSerial * _serial = NULL;

        double _active = 0;
        double _reactive = 0;
        double _voltage = 0;
        double _current = 0;
        double _apparent = 0;

        double _ratioP = V9261F_POWER_FACTOR;
        double _ratioC = V9261F_CURRENT_FACTOR;
        double _ratioV = V9261F_VOLTAGE_FACTOR;
        double _ratioR = V9261F_RPOWER_FACTOR;

        unsigned char _data[24];

};

#endif // SENSOR_SUPPORT && V9261F_SUPPORT
