/*
Damon Hart-Davis licenses this file to you
under the Apache Licence, Version 2.0 (the "Licence");
you may not use this file except in compliance
with the Licence. You may obtain a copy of the Licence at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the Licence is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the Licence for the
specific language governing permissions and limitations
under the Licence.

Author(s) / Copyright (s): Damon Hart-Davis 2014--2026
*/


#include <stdint.h>
#include <sys/param.h>


// Policy:
// 0) Primary aim is to support server load year round;
//    secondary is to support 'dump' load without hurting batteries.
// 1) not necessarily aiming to get to 100% SoC (VHIGH) whenever possible
//    as getting beyond ~80% SoC (HIGH) is relatively inefficient
//    see http://electronics.stackexchange.com/questions/153517/what-is-the-charge-discharge-watt-hour-wh-efficiency-for-lead-acid-batteries-a
// 2) but want to get to HIGH for a few hours, or FULL, each day if possible
//    (no more than a few days between hitting FULL) for health,
// 3) and dumping excess productively at this point may be a good energy use,
// 4) but never want to go below 50% SoC (VLOW) to avoid major damage,
// 5) and never want to sit low enough for long enough to sulphate.
//
// Note that the 'dump' load is assumed to be productive,
// eg taking a real load off-grid, rather than just wasted.


// If true, use a conservative, lower-DoD regime.
// True implicitly lowers the effort to help the grid and lower grid usage.
// DHD20260406: more aggressive use of LA to support winter house load.
static constexpr bool LESS_DoD = false;

// Do not dump when *usable* charge in battery lower than this.
// Reserves this much for running the RPi itself when poor insolation.
// This is charge *above* a 50 SoC baseline.
static constexpr uint8_t baseUsableSoCToDumpThrPC = LESS_DoD ? 30 : 20;

// Extra percentage of usable SoC to reserve w/poor forecast.
// This also applies to other warning signs (eg no recent VHIGH).
static constexpr uint8_t PF_reserve_PC = 5;


/* 2014/07/08 k8055/SheevaPlug voltage levels.
# Primary/SLA (gel) battery voltage thresholds in units of 0.1V.
# At/above 'BATTVH' (very high) means in absorption (a little below 14.0V)
# though maybe only ~13.6V in temperatures reaching 30C.
# Above 'High' means mainly full and charging/floating.  ~13.45V at 80% SoC.
# DHD20120725: set at 13.2V to allow for summer temperature compensation.
# Above 'Low' means probably reasonably full and/or charging but no 'excess'.
# At or below 'BATTVL' (very low) means urgent conservation needed.
# See LA SoC chart: http://solarjohn.blogspot.com/2007/03/measuring-battery-state-of-charge.html
BATTVH=136
BATTH=132
# A BATTL of 12.6V to 12.8V is 100%-full resting/light-discharge threshold.
BATTL=126
# 12.1V agrees with amber light on MS MPPT ctrl but only leaves ~1 day @ 40Ah.
# 12.2V suggests about 50% SoC minus slight drain by plug.
# 12.4V suggests maybe 60% SoC minus slight drain by plug.
BATTVL=123

# Secondary/LiFePO4 battery voltage thresholds in units of 0.1V.
# At or below 'BATTL2' the battery is not full nor nearly full.
# At or below 'BATTVL2' the battery is nearly empty.
BATTL2=131
BATTVL2=127

# Unless generation forecast is good then adjust thresholds to conserve further.
if [ ! -e $FORECASTGENGOOD ]; then
    BATTH=134
    BATTL=127
    BATTVL=125

    BATTL2=132
    BATTVL2=130
fi
*/

// Normal Battery 1 (gel lead-acid) thresholds.
// 12.1V agrees with amber light on MS MPPT ctrl but only leaves ~1 day @ 40Ah.
// 12.2V suggests about 50%/65% SoC unloaded/loaded (~empty if unloaded).
static constexpr int BATT1_RESTING_EFFECTIVE_EMPTY_mV = 12200;
// 12.3V suggests maybe 65%/80% SoC unloaded/loaded. Sulphation at/below this.
// 12.4V suggests maybe 75%/90% SoC unloaded/loaded. Sulphation at/below this?
// 12.5V suggests maybe 85%/100% SoC unloaded/loaded (~full if loaded).
// 12.6V to 12.8V is 100%-full resting/light-discharge threshold.
static constexpr int BATT1_RESTING_FULL_mV = 12700;
// 13.1V charging and maybe 50% SoC.
// 13.3V charging and maybe 70% SoC.
// 13.4V charging and maybe <80% SoC.  Continuous float possible.
// 13.6V charging and maybe 85% SoC.
// 13.7V charging and maybe 90% SoC or float; should be desulphating.
static constexpr int BATT1_CHARGING_FULL_mV = 13600; // Just below float.
// 14.0V charging and maybe 90+% SoC and gel absorption.
// At/above 13.1V means charging.
// At/above 13.6V means in bulk/absorption/float (actually a little below 14.0V,
// though maybe only ~13.6V in temperatures reaching 30C).
// Above 'High' means mainly full and charging/floating.  ~13.45V at 80% SoC.
// DHD20120725: set at ~13.2V to allow for summer temperature compensation.
// DHD20140813: if H thr much below 13.1V allows triggering in no sun.
// The 'not high' flag is an indication not to waste energy.
// The VL threshold may prematurely terminate
// overnight powering of (dump) load if too high.
// Roughly: 100mV = 10% SoC = 20% of usable capacity = 4 days of a 20-day store.
//
// Note some sources suggest 12.3V (2.05V/cell) is sulphation threshold:
//     http://batteryuniversity.com/learn/article/charging_the_lead_acid_battery
//
// VERY HIGH threshold: >90% and absorption/float charging and desulphating.
// Targetting 13.7V or above at the battery, allowing for cable losses.
// Long-term (not solar-limited daily float) can be ~13.4V or lower.
static constexpr int BATT1_THR_VH_mV = BATT1_CHARGING_FULL_mV; // 13600;
// HIGH threshold: ~80% SoC, charging.  Above this charging is especially lossy.
static constexpr int BATT1_THR_H_mV = 13450;
// LOW threshold: (LESS_DoD) ~85%/100% SoC un/loaded.
// DHD20230428: LESS_DoD L_mV 12450->12350 since even in Apr not dumping 24h.
static constexpr int BATT1_THR_L_mV  = LESS_DoD ? 12350 : 12200;
//static constexpr int BATT1_THR_L_mV  = LESS_DoD ? 12450 : 12300;
// VERY LOW threshold: (LESS_DoD) ~50% SoC un/loaded (effectively empty).
// All discretionary load should have been dropped; battery will sulphate.
// Note that there is no allowance for temperature or sag.
static constexpr int BATT1_THR_VL_mV = LESS_DoD ? 12200 : 12000;
// TODO: maybe allow sag when under dump load below VL.
// Abs min cut-out voltage.
static constexpr int BATT1_THR_ABSMIN_mV = LESS_DoD ? 11900 : 11000;
// Normal Battery 2 (LiPO4) thresholds.
// The 'not high' flag is an indication not to waste energy.
// HIGH threshold: ~100% SoC, no/light load.
//static constexpr int BATT2_THR_H_mV = 13180;
// VERY LOW threshold: nearly empty.
//static constexpr int BATT2_THR_VL_mV = 12900;

// Temperature compensation of lead-acid (eg) battery voltages mV/C.
// See:
// http://www.arttec.net/Solar_Mower/4_Electrical/Battery%20Charging.pdf
//     "discharge voltage [] may be depressed by as much as 0.5VDC..."
// https://www.victronenergy.com/upload/documents/Datasheet-GEL-and-AGM-Batteries-EN.pdf
//     "charge voltage should be reduced with increased temperature."
//     "Temperature compensation is required when the temperature of the
//     battery is expected to be less than 10C / 50F or more than 30C / 85F
//     during long periods of time."
//
// http://batteryuniversity.com/learn/article/charging_at_high_and_low_temperatures
//     Lower V-threshold by 3mV/C when hot.
//
// Note: Victron uses (for 6 cells) -24mV/C, SS-MPPT-15L uses -30mV/C.
// Battery University -18mV/C as above.
//static constexpr int LA_chargeTempComp_mVperC = -30;
// DHD20171230: trying reduced (~BU) compensation given actual 5C behaviour.
static constexpr int LA_chargeTempComp_mVperC = -15;
// Base temperature for lead-acid voltage compensation, Celsius.
static constexpr int LA_chargeTempComp_baseC = 20;

// Allowance above LOW before dumping to suppress slow oscillation.
// Enough to suppress effects of measurement precision/ulp also.
static constexpr uint16_t BATT1_DLdelta_mV = 150;

// Allowance in millivolts when dumping, ie added back to measured LA voltages.
// Assuming -90mV/A load compensation for lead-acid battery
// for a fairly low or cold ('soggy') battery, with higher-than-usual impedance.
// This allows for sag from battery to measurement point
// which may be at controller or at RPi depending on source.
// DHD20160917: was using -15mV/A, but observed behaviour suggests higher.
// DHD20161009: 50 allows dump oscillation.
// DHD20161013: 70 allows dump oscillation.
// DHD20161024: 80 allows a little dump oscillation, 2 or 3 terminal cycles.
// DHD20161107: 90 still: maybe temp comp for impedance needed (-ve vs temp).
// DHD20180523: 85 empirically, with battery at 20C...
static constexpr uint16_t sagLAmVperADumpCorr = 85;
// Increase in sag per C below base temperature (eg 20C), in milliOhms.
// Some of this (maybe the bulk) is from ionic conductivity.
// According to:
//     https://en.wikipedia.org/wiki/Electrical_conductivity_meter#Temperature_dependence
// "The temperature compensation slope for most naturally occurring
// waters is about 2%/°C ... " and lists H2SO4 as just below 2%.
// Also see graph:
//     https://www.mpoweruk.com/performance.htm
//static constexpr uint16_t sagLAmVperAperCDumpCorr = 16;
static constexpr uint16_t sagLAmVperAperCDumpCorr = 2;
// Compute LA internal impedance from temperature; strictly +ve.
// TODO: MAY NEED TO ADD IN AN INVERSE SoC-RELATED FACTOR.
// http://batteryuniversity.com/learn/archive/how_does_internal_resistance_affect_performance
//     A rest of a few hours will partially restore the battery
//     as the sulphate ions can replenish themselves.
//     The resistance change between full charge and discharge
//     is about 40%. Cold temperature increases the internal
//     resistance on all batteries and adds about 50%
//     between +30°C and -18°C to lead acid batteries.
constexpr uint16_t sagLADumpCorrmo(const int tempC)
    { return(uint16_t(MAX(1, int(sagLAmVperADumpCorr) + int(sagLAmVperAperCDumpCorr)*(20-tempC)))); }
// Allowance mV/A for supply drop due to controller+wiring impedance (mohm).
static constexpr uint16_t sagWiringmVperADumpCorr = 150;


// Compute (signed) temperature compensation for (12V) lead-acid battery.
// Goes down as temperature goes up; +ve below base temperature (~20C).
//   * s_T_batt  signed battery temperature in Celsius
constexpr int_fast16_t computeLATempComp_mV(const int_fast16_t s_T_batt)
    {
    return((LA_chargeTempComp_mVperC) *
                (s_T_batt - LA_chargeTempComp_baseC));
    }

// Compute usable charge for (12V) lead-acid battery.
//   * battmV  measured battery voltage in mV
//     (as near to the battery terminals as is available)
//   * sagB1mV  sag (if -ve, else rise) from load and internal impedance
//   * t_batt_temp_comp_mV  signed temperature compensation
//     for (12V) lead-acid battery, +ve below base temp; defaults to 0
constexpr uint8_t computeLAUsableSoC(
        const uint16_t battmV,
        const int_fast16_t sagB1mV = 0,
        const int_fast16_t t_batt_temp_comp_mV = 0)
    {
    return(uint8_t(MIN(100, MAX(0,
            (100 * (int_fast16_t(battmV)
                    - t_batt_temp_comp_mV
                    - sagB1mV
                    - int_fast16_t(BATT1_RESTING_EFFECTIVE_EMPTY_mV))) /
            (BATT1_RESTING_FULL_mV - BATT1_RESTING_EFFECTIVE_EMPTY_mV)
        ))));
    }
