BatteryMonitor.cpp revision bc102111845a48f79a4cf6ea058a0ca334cd613c
1/*
2 * Copyright (C) 2013 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#define LOG_TAG "healthd"
18
19#include "healthd.h"
20#include "BatteryMonitor.h"
21#include "BatteryPropertiesRegistrar.h"
22
23#include <dirent.h>
24#include <errno.h>
25#include <fcntl.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <unistd.h>
29#include <batteryservice/BatteryService.h>
30#include <cutils/klog.h>
31#include <utils/Errors.h>
32#include <utils/String8.h>
33#include <utils/Vector.h>
34
35#define POWER_SUPPLY_SUBSYSTEM "power_supply"
36#define POWER_SUPPLY_SYSFS_PATH "/sys/class/" POWER_SUPPLY_SUBSYSTEM
37
38namespace android {
39
40struct sysfsStringEnumMap {
41    char* s;
42    int val;
43};
44
45static int mapSysfsString(const char* str,
46                          struct sysfsStringEnumMap map[]) {
47    for (int i = 0; map[i].s; i++)
48        if (!strcmp(str, map[i].s))
49            return map[i].val;
50
51    return -1;
52}
53
54int BatteryMonitor::getBatteryStatus(const char* status) {
55    int ret;
56    struct sysfsStringEnumMap batteryStatusMap[] = {
57        { "Unknown", BATTERY_STATUS_UNKNOWN },
58        { "Charging", BATTERY_STATUS_CHARGING },
59        { "Discharging", BATTERY_STATUS_DISCHARGING },
60        { "Not charging", BATTERY_STATUS_NOT_CHARGING },
61        { "Full", BATTERY_STATUS_FULL },
62        { NULL, 0 },
63    };
64
65    ret = mapSysfsString(status, batteryStatusMap);
66    if (ret < 0) {
67        KLOG_WARNING(LOG_TAG, "Unknown battery status '%s'\n", status);
68        ret = BATTERY_STATUS_UNKNOWN;
69    }
70
71    return ret;
72}
73
74int BatteryMonitor::getBatteryHealth(const char* status) {
75    int ret;
76    struct sysfsStringEnumMap batteryHealthMap[] = {
77        { "Unknown", BATTERY_HEALTH_UNKNOWN },
78        { "Good", BATTERY_HEALTH_GOOD },
79        { "Overheat", BATTERY_HEALTH_OVERHEAT },
80        { "Dead", BATTERY_HEALTH_DEAD },
81        { "Over voltage", BATTERY_HEALTH_OVER_VOLTAGE },
82        { "Unspecified failure", BATTERY_HEALTH_UNSPECIFIED_FAILURE },
83        { "Cold", BATTERY_HEALTH_COLD },
84        { NULL, 0 },
85    };
86
87    ret = mapSysfsString(status, batteryHealthMap);
88    if (ret < 0) {
89        KLOG_WARNING(LOG_TAG, "Unknown battery health '%s'\n", status);
90        ret = BATTERY_HEALTH_UNKNOWN;
91    }
92
93    return ret;
94}
95
96int BatteryMonitor::readFromFile(const String8& path, char* buf, size_t size) {
97    char *cp = NULL;
98
99    if (path.isEmpty())
100        return -1;
101    int fd = open(path.string(), O_RDONLY, 0);
102    if (fd == -1) {
103        KLOG_ERROR(LOG_TAG, "Could not open '%s'\n", path.string());
104        return -1;
105    }
106
107    ssize_t count = TEMP_FAILURE_RETRY(read(fd, buf, size));
108    if (count > 0)
109            cp = (char *)memrchr(buf, '\n', count);
110
111    if (cp)
112        *cp = '\0';
113    else
114        buf[0] = '\0';
115
116    close(fd);
117    return count;
118}
119
120BatteryMonitor::PowerSupplyType BatteryMonitor::readPowerSupplyType(const String8& path) {
121    const int SIZE = 128;
122    char buf[SIZE];
123    int length = readFromFile(path, buf, SIZE);
124    BatteryMonitor::PowerSupplyType ret;
125    struct sysfsStringEnumMap supplyTypeMap[] = {
126            { "Unknown", ANDROID_POWER_SUPPLY_TYPE_UNKNOWN },
127            { "Battery", ANDROID_POWER_SUPPLY_TYPE_BATTERY },
128            { "UPS", ANDROID_POWER_SUPPLY_TYPE_AC },
129            { "Mains", ANDROID_POWER_SUPPLY_TYPE_AC },
130            { "USB", ANDROID_POWER_SUPPLY_TYPE_USB },
131            { "USB_DCP", ANDROID_POWER_SUPPLY_TYPE_AC },
132            { "USB_CDP", ANDROID_POWER_SUPPLY_TYPE_AC },
133            { "USB_ACA", ANDROID_POWER_SUPPLY_TYPE_AC },
134            { "Wireless", ANDROID_POWER_SUPPLY_TYPE_WIRELESS },
135            { NULL, 0 },
136    };
137
138    if (length <= 0)
139        return ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
140
141    ret = (BatteryMonitor::PowerSupplyType)mapSysfsString(buf, supplyTypeMap);
142    if (ret < 0)
143        ret = ANDROID_POWER_SUPPLY_TYPE_UNKNOWN;
144
145    return ret;
146}
147
148bool BatteryMonitor::getBooleanField(const String8& path) {
149    const int SIZE = 16;
150    char buf[SIZE];
151
152    bool value = false;
153    if (readFromFile(path, buf, SIZE) > 0) {
154        if (buf[0] != '0') {
155            value = true;
156        }
157    }
158
159    return value;
160}
161
162int BatteryMonitor::getIntField(const String8& path) {
163    const int SIZE = 128;
164    char buf[SIZE];
165
166    int value = 0;
167    if (readFromFile(path, buf, SIZE) > 0) {
168        value = strtol(buf, NULL, 0);
169    }
170    return value;
171}
172
173bool BatteryMonitor::update(void) {
174    struct BatteryProperties props;
175    bool logthis;
176
177    props.chargerAcOnline = false;
178    props.chargerUsbOnline = false;
179    props.chargerWirelessOnline = false;
180    props.batteryStatus = BATTERY_STATUS_UNKNOWN;
181    props.batteryHealth = BATTERY_HEALTH_UNKNOWN;
182    props.batteryCurrentNow = INT_MIN;
183    props.batteryChargeCounter = INT_MIN;
184
185    if (!mHealthdConfig->batteryPresentPath.isEmpty())
186        props.batteryPresent = getBooleanField(mHealthdConfig->batteryPresentPath);
187    else
188        props.batteryPresent = true;
189
190    props.batteryLevel = getIntField(mHealthdConfig->batteryCapacityPath);
191    props.batteryVoltage = getIntField(mHealthdConfig->batteryVoltagePath) / 1000;
192
193    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty())
194        props.batteryCurrentNow = getIntField(mHealthdConfig->batteryCurrentNowPath);
195
196    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty())
197        props.batteryChargeCounter = getIntField(mHealthdConfig->batteryChargeCounterPath);
198
199    props.batteryTemperature = getIntField(mHealthdConfig->batteryTemperaturePath);
200
201    const int SIZE = 128;
202    char buf[SIZE];
203    String8 btech;
204
205    if (readFromFile(mHealthdConfig->batteryStatusPath, buf, SIZE) > 0)
206        props.batteryStatus = getBatteryStatus(buf);
207
208    if (readFromFile(mHealthdConfig->batteryHealthPath, buf, SIZE) > 0)
209        props.batteryHealth = getBatteryHealth(buf);
210
211    if (readFromFile(mHealthdConfig->batteryTechnologyPath, buf, SIZE) > 0)
212        props.batteryTechnology = String8(buf);
213
214    unsigned int i;
215
216    for (i = 0; i < mChargerNames.size(); i++) {
217        String8 path;
218        path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH,
219                          mChargerNames[i].string());
220
221        if (readFromFile(path, buf, SIZE) > 0) {
222            if (buf[0] != '0') {
223                path.clear();
224                path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH,
225                                  mChargerNames[i].string());
226                switch(readPowerSupplyType(path)) {
227                case ANDROID_POWER_SUPPLY_TYPE_AC:
228                    props.chargerAcOnline = true;
229                    break;
230                case ANDROID_POWER_SUPPLY_TYPE_USB:
231                    props.chargerUsbOnline = true;
232                    break;
233                case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
234                    props.chargerWirelessOnline = true;
235                    break;
236                default:
237                    KLOG_WARNING(LOG_TAG, "%s: Unknown power supply type\n",
238                                 mChargerNames[i].string());
239                }
240            }
241        }
242    }
243
244    logthis = !healthd_board_battery_update(&props);
245
246    if (logthis) {
247        char dmesgline[256];
248        snprintf(dmesgline, sizeof(dmesgline),
249                 "battery l=%d v=%d t=%s%d.%d h=%d st=%d",
250                 props.batteryLevel, props.batteryVoltage,
251                 props.batteryTemperature < 0 ? "-" : "",
252                 abs(props.batteryTemperature / 10),
253                 abs(props.batteryTemperature % 10), props.batteryHealth,
254                 props.batteryStatus);
255
256        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
257            char b[20];
258
259            snprintf(b, sizeof(b), " c=%d", props.batteryCurrentNow / 1000);
260            strlcat(dmesgline, b, sizeof(dmesgline));
261        }
262
263        KLOG_INFO(LOG_TAG, "%s chg=%s%s%s\n", dmesgline,
264                  props.chargerAcOnline ? "a" : "",
265                  props.chargerUsbOnline ? "u" : "",
266                  props.chargerWirelessOnline ? "w" : "");
267    }
268
269    if (mBatteryPropertiesRegistrar != NULL)
270        mBatteryPropertiesRegistrar->notifyListeners(props);
271
272    return props.chargerAcOnline | props.chargerUsbOnline |
273            props.chargerWirelessOnline;
274}
275
276status_t BatteryMonitor::getProperty(int id, struct BatteryProperty *val) {
277    status_t ret = BAD_VALUE;
278
279    switch(id) {
280    case BATTERY_PROP_CHARGE_COUNTER:
281        if (!mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
282            val->valueInt =
283                getIntField(mHealthdConfig->batteryChargeCounterPath);
284            ret = NO_ERROR;
285        } else {
286            ret = NAME_NOT_FOUND;
287        }
288        break;
289
290    case BATTERY_PROP_CURRENT_NOW:
291        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
292            val->valueInt =
293                getIntField(mHealthdConfig->batteryCurrentNowPath);
294            ret = NO_ERROR;
295        } else {
296            ret = NAME_NOT_FOUND;
297        }
298        break;
299
300    case BATTERY_PROP_CURRENT_AVG:
301        if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
302            val->valueInt =
303                getIntField(mHealthdConfig->batteryCurrentAvgPath);
304            ret = NO_ERROR;
305        } else {
306            ret = NAME_NOT_FOUND;
307        }
308        break;
309
310    default:
311        break;
312    }
313
314    if (ret != NO_ERROR)
315        val->valueInt = INT_MIN;
316
317    return ret;
318}
319
320void BatteryMonitor::init(struct healthd_config *hc, bool nosvcmgr) {
321    String8 path;
322
323    mHealthdConfig = hc;
324    DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
325    if (dir == NULL) {
326        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
327    } else {
328        struct dirent* entry;
329
330        while ((entry = readdir(dir))) {
331            const char* name = entry->d_name;
332
333            if (!strcmp(name, ".") || !strcmp(name, ".."))
334                continue;
335
336            char buf[20];
337            // Look for "type" file in each subdirectory
338            path.clear();
339            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);
340            switch(readPowerSupplyType(path)) {
341            case ANDROID_POWER_SUPPLY_TYPE_AC:
342            case ANDROID_POWER_SUPPLY_TYPE_USB:
343            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
344                path.clear();
345                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
346                if (access(path.string(), R_OK) == 0)
347                    mChargerNames.add(String8(name));
348                break;
349
350            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
351                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
352                    path.clear();
353                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
354                                      name);
355                    if (access(path, R_OK) == 0)
356                        mHealthdConfig->batteryStatusPath = path;
357                }
358
359                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
360                    path.clear();
361                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
362                                      name);
363                    if (access(path, R_OK) == 0)
364                        mHealthdConfig->batteryHealthPath = path;
365                }
366
367                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
368                    path.clear();
369                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
370                                      name);
371                    if (access(path, R_OK) == 0)
372                        mHealthdConfig->batteryPresentPath = path;
373                }
374
375                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
376                    path.clear();
377                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
378                                      name);
379                    if (access(path, R_OK) == 0)
380                        mHealthdConfig->batteryCapacityPath = path;
381                }
382
383                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
384                    path.clear();
385                    path.appendFormat("%s/%s/voltage_now",
386                                      POWER_SUPPLY_SYSFS_PATH, name);
387                    if (access(path, R_OK) == 0) {
388                        mHealthdConfig->batteryVoltagePath = path;
389                    } else {
390                        path.clear();
391                        path.appendFormat("%s/%s/batt_vol",
392                                          POWER_SUPPLY_SYSFS_PATH, name);
393                        if (access(path, R_OK) == 0)
394                            mHealthdConfig->batteryVoltagePath = path;
395                    }
396                }
397
398                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
399                    path.clear();
400                    path.appendFormat("%s/%s/current_now",
401                                      POWER_SUPPLY_SYSFS_PATH, name);
402                    if (access(path, R_OK) == 0)
403                        mHealthdConfig->batteryCurrentNowPath = path;
404                }
405
406                if (mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
407                    path.clear();
408                    path.appendFormat("%s/%s/current_avg",
409                                      POWER_SUPPLY_SYSFS_PATH, name);
410                    if (access(path, R_OK) == 0)
411                        mHealthdConfig->batteryCurrentAvgPath = path;
412                }
413
414                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
415                    path.clear();
416                    path.appendFormat("%s/%s/charge_counter",
417                                      POWER_SUPPLY_SYSFS_PATH, name);
418                    if (access(path, R_OK) == 0)
419                        mHealthdConfig->batteryChargeCounterPath = path;
420                }
421
422                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
423                    path.clear();
424                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
425                                      name);
426                    if (access(path, R_OK) == 0) {
427                        mHealthdConfig->batteryTemperaturePath = path;
428                    } else {
429                        path.clear();
430                        path.appendFormat("%s/%s/batt_temp",
431                                          POWER_SUPPLY_SYSFS_PATH, name);
432                        if (access(path, R_OK) == 0)
433                            mHealthdConfig->batteryTemperaturePath = path;
434                    }
435                }
436
437                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
438                    path.clear();
439                    path.appendFormat("%s/%s/technology",
440                                      POWER_SUPPLY_SYSFS_PATH, name);
441                    if (access(path, R_OK) == 0)
442                        mHealthdConfig->batteryTechnologyPath = path;
443                }
444
445                break;
446
447            case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
448                break;
449            }
450        }
451        closedir(dir);
452    }
453
454    if (!mChargerNames.size())
455        KLOG_ERROR(LOG_TAG, "No charger supplies found\n");
456    if (mHealthdConfig->batteryStatusPath.isEmpty())
457        KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
458    if (mHealthdConfig->batteryHealthPath.isEmpty())
459        KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
460    if (mHealthdConfig->batteryPresentPath.isEmpty())
461        KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
462    if (mHealthdConfig->batteryCapacityPath.isEmpty())
463        KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
464    if (mHealthdConfig->batteryVoltagePath.isEmpty())
465        KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
466    if (mHealthdConfig->batteryTemperaturePath.isEmpty())
467        KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
468    if (mHealthdConfig->batteryTechnologyPath.isEmpty())
469        KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
470
471    if (nosvcmgr == false) {
472            mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar(this);
473            mBatteryPropertiesRegistrar->publish();
474    }
475}
476
477}; // namespace android
478