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