1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "BatteryRechargingControl.h"
18 
19 namespace device {
20 namespace google {
21 namespace bonito {
22 namespace health {
23 
24 static const std::string kChargerStatus = "sys/class/power_supply/battery/status";
25 static const std::string kStatusIsFull = "Full";
26 static const std::string kStatusIsCharging = "Charging";
27 static constexpr int kTransitionTime = 15 * 60;  // Seconds
28 static constexpr int kFullSoc = 100;
29 
BatteryRechargingControl()30 BatteryRechargingControl::BatteryRechargingControl() {
31     state_ = INACTIVE;
32     recharge_soc_ = 0;
33 }
34 
mapSysfsString(const char * str,struct sysfsStringEnumMap map[])35 int BatteryRechargingControl::mapSysfsString(const char *str, struct sysfsStringEnumMap map[]) {
36     for (int i = 0; map[i].s; i++)
37         if (!strncmp(str, map[i].s, strlen(map[i].s)))
38             return map[i].val;
39 
40     return -1;
41 }
42 
getBatteryStatus(const char * status)43 int BatteryRechargingControl::getBatteryStatus(const char *status) {
44     int ret;
45     struct sysfsStringEnumMap batteryStatusMap[] = {
46         {"Unknown", android::BATTERY_STATUS_UNKNOWN},
47         {"Charging", android::BATTERY_STATUS_CHARGING},
48         {"Discharging", android::BATTERY_STATUS_DISCHARGING},
49         {"Not charging", android::BATTERY_STATUS_NOT_CHARGING},
50         {"Full", android::BATTERY_STATUS_FULL},
51         {NULL, 0},
52     };
53 
54     ret = mapSysfsString(status, batteryStatusMap);
55     if (ret < 0) {
56         LOG(ERROR) << "Unknown battery status: " << status;
57         ret = android::BATTERY_STATUS_UNKNOWN;
58     }
59 
60     return ret;
61 }
62 
getTime(void)63 int64_t BatteryRechargingControl::getTime(void) {
64     return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
65 }
66 
RemapSOC(int soc)67 int BatteryRechargingControl::RemapSOC(int soc) {
68     double diff_sec = getTime() - start_time_;
69     double ret_soc =
70         round(soc * (diff_sec / kTransitionTime) + kFullSoc * (1 - (diff_sec / kTransitionTime)));
71     LOG(INFO) << "RemapSOC: " << ret_soc;
72     return ret_soc;
73 }
74 
updateBatteryProperties(struct android::BatteryProperties * props)75 void BatteryRechargingControl::updateBatteryProperties(struct android::BatteryProperties *props) {
76     std::string charger_status;
77     double elapsed_time;
78     int cur_soc;
79 
80     if (!android::base::ReadFileToString(kChargerStatus, &charger_status)) {
81         LOG(ERROR) << "Cannot read the charger status";
82         return;
83     }
84 
85     charger_status = android::base::Trim(charger_status);
86     props->batteryStatus = getBatteryStatus(charger_status.c_str());
87 
88     if ((state_ == INACTIVE) && (props->batteryLevel < kFullSoc))
89         return;
90 
91     LOG(INFO) << "Entry state_: " << state_ << " charger_status: " << charger_status
92               << " batteryLevel: " << props->batteryLevel;
93     switch (state_) {
94         case INACTIVE:
95             state_ = WAIT_EOC;
96             recharge_soc_ = 0;
97         case WAIT_EOC:
98             if (props->batteryLevel != kFullSoc) {
99                 state_ = INACTIVE;
100                 recharge_soc_ = 0;
101             } else if (charger_status == kStatusIsFull) {
102                 state_ = RECHARGING_CYCLE;
103                 props->batteryLevel = kFullSoc;
104             } else if (charger_status != kStatusIsCharging) {
105                 // charging stopped, assume no more power source
106                 start_time_ = getTime();
107                 state_ = NO_POWER_SOURCE;
108                 props->batteryLevel = RemapSOC(props->batteryLevel);
109             }
110             break;
111         case RECHARGING_CYCLE:
112             if (charger_status == kStatusIsFull) {
113                 recharge_soc_ = 0;
114                 props->batteryLevel = kFullSoc;
115                 break;
116             } else if (charger_status == kStatusIsCharging) {
117                 // Recharging cycle start.
118                 if (recharge_soc_ == 0) {
119                     recharge_soc_ = props->batteryLevel;
120                     props->batteryLevel = kFullSoc;
121                 } else {
122                     if (props->batteryLevel < recharge_soc_) {
123                         // overload condition
124                         start_time_ = getTime();
125                         state_ = OVER_LOADING;
126                         props->batteryLevel = RemapSOC(props->batteryLevel);
127                     } else {
128                         props->batteryLevel = kFullSoc;
129                     }
130                 }
131             } else {
132                 // charging stopped, assume no more power source
133                 start_time_ = getTime();
134                 state_ = NO_POWER_SOURCE;
135                 props->batteryLevel = RemapSOC(props->batteryLevel);
136             }
137             break;
138         case OVER_LOADING:
139         case NO_POWER_SOURCE:
140             cur_soc = props->batteryLevel;
141             elapsed_time = getTime() - start_time_;
142             if (elapsed_time > kTransitionTime) {
143                 LOG(INFO) << "Time is up, leave remap";
144                 state_ = INACTIVE;
145                 break;
146             } else {
147                 LOG(INFO) << "Diff time: " << elapsed_time;
148                 int battery_level = RemapSOC(props->batteryLevel);
149                 if ((battery_level == props->batteryLevel) && (battery_level != kFullSoc)) {
150                     state_ = INACTIVE;
151                     break;
152                 }
153                 props->batteryLevel = battery_level;
154             }
155             if (charger_status == kStatusIsCharging) {
156                 if ((props->batteryLevel == kFullSoc) && (cur_soc >= recharge_soc_)) {
157                     // When user plug in charger and the ret_soc is still 100%
158                     // Change condition to Recharging cycle to avoid the SOC
159                     // show lower than 100%. (Keep 100%)
160                     state_ = RECHARGING_CYCLE;
161                     recharge_soc_ = props->batteryLevel;
162                 }
163             }
164             break;
165         default:
166             state_ = WAIT_EOC;
167             break;
168     }
169     LOG(INFO) << "Exit state_: " << state_ << " batteryLevel: " << props->batteryLevel;
170 }
171 
172 }  // namespace health
173 }  // namespace bonito
174 }  // namespace google
175 }  // namespace device
176