Remaining Useful Life (RUL) implementation 22/207422/4
authorAmandeep Chauhan <a.chauhan@samsung.com>
Mon, 3 Jun 2019 14:48:17 +0000 (16:48 +0200)
committerMichal Bloch <m.bloch@partner.samsung.com>
Fri, 22 Nov 2019 19:51:00 +0000 (19:51 +0000)
RUL implementation for monitoring battery health
and detecting when battery needs to be replaced

Change-Id: Id286f292630ede1670be2859e4439f236551631f
Signed-off-by: Amandeep Chauhan <a.chauhan@samsung.com>
packaging/resourced.spec
src/CMakeLists.txt
src/heart/heart-battery.c

index c084577..010e148 100644 (file)
@@ -42,6 +42,7 @@ BuildRequires:  pkgconfig(capi-system-info)
 BuildRequires:  pkgconfig(libtzplatform-config)
 BuildRequires:  pkgconfig(storage)
 BuildRequires:  pkgconfig(libgum)
+BuildRequires:  pkgconfig(capi-system-device)
 
 #only for data types
 BuildRequires:  pkgconfig(tapi)
index 129472b..4c54ad6 100644 (file)
@@ -78,6 +78,7 @@ SET(REQUIRES_LIST ${REQUIRES_LIST}
        storage
        libgum
        libtzplatform-config
+       capi-system-device
   )
 
 INCLUDE(FindPkgConfig)
index c3d8a51..d1137b2 100644 (file)
@@ -33,6 +33,8 @@
 #include <sys/time.h>
 #include <sys/types.h>
 #include <math.h>
+#include <device/display.h>
+#include <device/callback.h>
 
 #include "proc-common.h"
 #include "notifier.h"
 #define HEART_BATTERY_DATA_FILE                HEART_FILE_PATH"/.battery_data.dat"
 #define VCONFKEY_HEART_BATTERY_DEVICE_MODE                     "db/setting/psmode"
 
+/*
+ * Remaining Useful Life prediction logic
+ */
+#define RUL_DATA_FILE                  HEART_FILE_PATH"/.rul_data.dat"
+#define RUL_CHARGING_CYCLES_FILE                       HEART_FILE_PATH"/.charging_cycles.dat"
+#define RUL_MIN_CAPACITY_DIFF                  50
+#define RUL_DISPLAY_ON_CUTOFF_RATIO                    0.1f
+#define RUL_DATA_SIZE                  32
+
 enum {
        TA     = 0,     /* prediction based on total data average */
        PCB    = 1,     /* prediction with physiological behaviors */
@@ -142,6 +153,28 @@ enum {
        DEFAULT_VALUE_MAX = 3,
 };
 
+
+/* Structure to calculate percentage time LCD on during charging */
+struct rul_lcd_data {
+       display_state_e state;
+       long on_time;
+       long on_start_time;
+       long on_stop_time;
+};
+
+/* Structure to store RUL related charging info  */
+struct rul_info {
+       long charging_start_time;
+       long charging_stop_time;
+       float raw_capacity; /* capacity (mAh) accumulation during charging */
+       int soc_start; /* SoC(%) value when charging starts */
+       int soc_stop; /* SoC(%) value when charging stops */
+       int charging_cycles; /* Number of charging cycles */
+       int curr_soc; /* Current SoC %, compare with prev_soc to determine if charging */
+       int prev_soc; /* Previous value of SoC % */
+       struct rul_lcd_data lcd_data; /* LCD related data */
+};
+
 struct battery_used {
        time_t used_time_sec; /* seconds on battery */
        time_t last_update_time;
@@ -306,6 +339,22 @@ static bool first_level_change = FALSE;
  */
 static char battery_header[BATTERY_LINE_MAX] = "BATTERY";
 
+/*
+ * Remaining Useful Life (RUL) prediction logic
+ */
+static int logic_rul;
+struct rul_info rul;
+
+int heart_battery_get_capacity(void)
+{
+       return batt_stat.curr_capacity;
+}
+
+enum charger_status_type heart_battery_get_charger_status(void)
+{
+       return batt_stat.curr_charger_status;
+}
+
 static inline void heart_battery_set_usage_reset_stime(int history, time_t start_time)
 {
        batt_stat.batt_reset_usage[history].start_time = start_time;
@@ -1940,6 +1989,176 @@ static int heart_battery_add_capacity(int capacity)
        return RESOURCED_ERROR_NONE;
 }
 
+static void rul_display_state_changed_cb(device_callback_e type, void *value, void *user_data)
+{
+       int display_state = (int) ((intptr_t) value), charger_status;
+       /* 0 : on, 1 : dim, 2: off */
+
+       /*
+        * If charger_status==CHARGING and raw capacity calculation ongoing,
+        * then, handle display_on time for RUL
+        */
+
+       charger_status = heart_battery_get_charger_status();
+       if ((charger_status == CHARGING) && (rul.charging_start_time != 0)) {
+               if (type != DEVICE_CALLBACK_DISPLAY_STATE)
+                       return;
+
+               if (display_state == DISPLAY_STATE_NORMAL) {
+                       _I("DISPLAY: NORMAL/DIM");
+                       rul.lcd_data.state = DISPLAY_STATE_NORMAL;
+                       if (rul.lcd_data.on_start_time == 0) {
+                               rul.lcd_data.on_start_time = logging_get_time(CLOCK_BOOTTIME);
+                       }
+               } else if (display_state == DISPLAY_STATE_SCREEN_OFF) {
+                       _I("DISPLAY: OFF");
+                       rul.lcd_data.state = DISPLAY_STATE_SCREEN_OFF;
+                       if (rul.lcd_data.on_start_time > 0) {
+                               rul.lcd_data.on_stop_time = logging_get_time(CLOCK_BOOTTIME);
+                               rul.lcd_data.on_time += rul.lcd_data.on_stop_time - rul.lcd_data.on_start_time;
+                               rul.lcd_data.on_start_time = 0;
+                               rul.lcd_data.on_stop_time = 0;
+                       }
+               }
+       }
+}
+
+static void rul_write_data_to_file(void)
+{
+       int len = 0;
+       char buff[RUL_DATA_SIZE] = {0};
+       _cleanup_fclose_ FILE *fp = NULL, *fp1 = NULL;
+       float fullcapnom = 0, fullcapnom_mAh = 0;
+
+       /* Update count of Charging cycles*/
+       rul.charging_cycles += 1;
+
+       /* Calculate normalized capacity, i.e., extrapolate for 0% to 100% */
+       fullcapnom = rul.raw_capacity / ((rul.soc_stop/100.0f) - (rul.soc_start/100.0f));
+       fullcapnom_mAh = fullcapnom / 3.6f;
+       _I("rul : raw_capacity = %.4f As, fullcapnom = %.4f As = %.4f mAh", rul.raw_capacity, fullcapnom, fullcapnom_mAh);
+
+       /* Update RUL data file */
+       fp = fopen(RUL_DATA_FILE, "a");
+       if (fp == NULL) {
+               _E("Could not open file - RUL_DATA_FILE");
+               return;
+       }
+
+       len = snprintf(buff, RUL_DATA_SIZE, "%d %.4f\n", rul.charging_cycles, fullcapnom_mAh);
+
+       if (len < RUL_DATA_SIZE)
+               fputs(buff, fp);
+
+       /* Write Charging cycles count to file */
+       fp1 = fopen(RUL_CHARGING_CYCLES_FILE, "w");
+       if (fp1 == NULL) {
+               _E("Could not open file for write - RUL_CHARGING_CYCLES_FILE");
+               return;
+       }
+       fprintf(fp1, "%d", rul.charging_cycles);
+
+}
+
+static void rul_charging_cycle_end_work(void)
+{
+       int capacity;
+       long total_charging_time;
+       bool lcd_on_time_check;
+       float lcd_on_ratio = 1.0f;
+
+       capacity = heart_battery_get_capacity();
+
+       rul.soc_stop = capacity;
+       rul.charging_stop_time = logging_get_time(CLOCK_BOOTTIME);
+
+       /* If LCD was on before disconnecting charger, update display_on time */
+       if (rul.lcd_data.on_start_time != 0) {
+               rul.lcd_data.on_stop_time = logging_get_time(CLOCK_BOOTTIME);
+               rul.lcd_data.on_time += rul.lcd_data.on_stop_time - rul.lcd_data.on_start_time;
+       }
+
+       /* Max LCD ON Time ratio criteria met ? */
+       total_charging_time = rul.charging_stop_time - rul.charging_start_time;
+       if (total_charging_time != 0)
+               lcd_on_ratio = (rul.lcd_data.on_time / (total_charging_time * 1.0f));
+       lcd_on_time_check = lcd_on_ratio <= RUL_DISPLAY_ON_CUTOFF_RATIO;
+
+       /* write data to file if condititons met*/
+       if ((rul.charging_start_time != 0) && (lcd_on_time_check == true) &&
+           (rul.soc_stop - rul.soc_start >= RUL_MIN_CAPACITY_DIFF))
+               rul_write_data_to_file();
+
+       /* init for next charging cycle */
+       rul.soc_start = 0;
+       rul.soc_stop = 0;
+       rul.charging_start_time = 0;
+       rul.charging_stop_time = 0;
+       rul.lcd_data.on_start_time = 0;
+       rul.lcd_data.on_stop_time = 0;
+       rul.lcd_data.on_time = 0;
+}
+
+static void rul_calculate_raw_capacity(void)
+{
+       /*
+        * This function updates Raw Capacity every 1% SoC change.
+        *
+        * Upon first time 1% SoC change, initialize rul_info variables.
+        *
+        * Subsequently, Update Raw Capacity using time interval and
+        * current_now sys node values.
+        *
+        * If capacity = 100%, dump collected data
+        */
+
+       long rul_curr_time, rul_time_interval;
+       static long prev_charging_time;
+       int fg_curr_inst, capacity, ret;
+       float fg_curr_inst_f;
+       static float prev_fg_curr_inst_f = 0.0f;
+
+       capacity = heart_battery_get_capacity();
+
+       if (rul.charging_start_time == 0) {
+               rul.charging_start_time = logging_get_time(CLOCK_BOOTTIME);
+               rul.soc_start = capacity;
+               rul.raw_capacity = 0.0f;
+               prev_charging_time = rul.charging_start_time;
+               prev_fg_curr_inst_f = 0.0f;
+               rul.lcd_data.on_start_time = 0;
+               rul.lcd_data.on_stop_time = 0;
+               rul.lcd_data.on_time = 0;
+
+               /* Get display status at time of initiating RUL calculations */
+               ret = device_display_get_state(&rul.lcd_data.state);
+               if (ret != DEVICE_ERROR_NONE) {
+                       _E("Failed to get device display state with err = %d", ret);
+                       return;
+               }
+               if ((rul.lcd_data.state == DISPLAY_STATE_NORMAL) || (rul.lcd_data.state == DISPLAY_STATE_SCREEN_DIM))
+                       rul.lcd_data.on_start_time = logging_get_time(CLOCK_BOOTTIME);
+       } else {
+               rul_curr_time = logging_get_time(CLOCK_BOOTTIME);
+               rul_time_interval = rul_curr_time - prev_charging_time;
+               prev_charging_time = rul_curr_time;
+
+               ret = fread_int("/sys/class/power_supply/battery/current_now", &fg_curr_inst);
+               if (ret != RESOURCED_ERROR_NONE) {
+                       _E("This device doesn't support FG CURRENT_NOW information. Disable LOGIC_RUL");
+                       return;
+               }
+
+               fg_curr_inst_f = fg_curr_inst / 1000.0f;
+               rul.raw_capacity += ((fg_curr_inst_f + prev_fg_curr_inst_f) / 2.0f)*rul_time_interval;
+               prev_fg_curr_inst_f = fg_curr_inst_f;
+       }
+
+       if (capacity == 100) {
+               rul_charging_cycle_end_work();
+       }
+}
+
 /* ============================ DBUS -> DEVICED on demand ==================== */
 
 static int heart_battery_direct_get_capacity(void)
@@ -2015,6 +2234,20 @@ static void heart_battery_capacity_status(GVariant *params)
                        first_level_change = FALSE;
                }
        }
+       if (logic_rul) {
+               /* Update prev_soc, curr_soc */
+               rul.prev_soc = rul.curr_soc;
+               rul.curr_soc = capacity;
+
+               /*
+                * If charger_status==CHARGING and SOC_INCREASING, start/continue raw capacity calculation.
+                * Else, if raw capacity calculation was ongoing, then dump collected data.
+                */
+               if ((batt_stat.curr_charger_status == CHARGING) && (rul.curr_soc >= rul.prev_soc))
+                       rul_calculate_raw_capacity();
+               else if (rul.charging_start_time != 0)
+                       rul_charging_cycle_end_work();
+       }
 }
 
 static void heart_battery_charger_status(GVariant *params)
@@ -2081,6 +2314,14 @@ static void heart_battery_charger_status(GVariant *params)
                        heart_battery_cal_charging_rem_time();
                }
        }
+       if (logic_rul) {
+               /*
+                * If charger disconnected and raw capacity calculation was ongoing,
+                * then dump collected data.
+                */
+               if ((charger_status == DISCHARGING) && (rul.charging_start_time != 0))
+                       rul_charging_cycle_end_work();
+       }
 }
 
 /* =========================  DBUS -> DEVICED handler END ==================== */
@@ -2684,6 +2925,9 @@ static int heart_battery_config(struct parse_result *result, void *user_data)
                if (!strncmp(result->name, "LOGIC_V2", sizeof("LOGIC_V2") + 1)) {
                        logic_v2 = atoi(result->value);
                        _I("logic_v2 : %d", logic_v2);
+               } else if (!strncmp(result->name, "LOGIC_RUL", sizeof("LOGIC_RUL") + 1)) {
+                       logic_rul = atoi(result->value);
+                       _I("logic_rul : %d", logic_rul);
                } else if (!strncmp(result->name, "DISCHARGE_FAST", sizeof("DISCHARGE_FAST") + 1)) {
                        discharge_fast = atoi(result->value);
                        _I("discharge_fast: %d", discharge_fast);
@@ -2772,6 +3016,47 @@ static void heart_read_battery_total_capacity(void)
                        batt_capacity, battery_header);
 }
 
+static void rul_data_init(void)
+{
+       _cleanup_fclose_ FILE *fp = NULL;
+       int ret;
+
+       rul.charging_start_time = 0;
+       rul.charging_stop_time = 0;
+       rul.raw_capacity = 0;
+       rul.soc_start = 0;
+       rul.soc_stop = 0;
+       rul.curr_soc = heart_battery_direct_get_capacity();
+       rul.prev_soc = rul.curr_soc;
+
+       /* Init LCD related data */
+       rul.lcd_data.on_start_time = 0;
+       rul.lcd_data.on_stop_time = 0;
+       rul.lcd_data.on_time = 0;
+       ret = device_display_get_state(&rul.lcd_data.state);
+       if (ret != DEVICE_ERROR_NONE) {
+               _E("Failed to get device display state with err = %d", ret);
+               return;
+       }
+
+       /* Register callback for LCD state change */
+       device_add_callback(DEVICE_CALLBACK_DISPLAY_STATE, rul_display_state_changed_cb, NULL);
+
+       /* Read Charging cycles count from file */
+       fp = fopen(RUL_CHARGING_CYCLES_FILE, "r");
+       if (fp == NULL) {
+               _E("Can't open RUL_CHARGING_CYCLES_FILE");
+               rul.charging_cycles = 0;
+               return;
+       }
+
+       if (fscanf(fp, "%d", &rul.charging_cycles) != 1) {
+               _E("Unable to assign charging cycles from file");
+               rul.charging_cycles = 0;
+               return;
+       }
+}
+
 static int heart_battery_init(void *data)
 {
        int ret;
@@ -2805,6 +3090,9 @@ static int heart_battery_init(void *data)
 
        heart_battery_status_init();
 
+       if (logic_rul)
+               rul_data_init();
+
        register_notifier(RESOURCED_NOTIFIER_LOW_BATTERY, low_battery_handler);
        register_notifier(RESOURCED_NOTIFIER_DATA_RESET, heart_battery_reset);