BatteryMonitor.cpp revision 6f5b47f9144960412b0fb6bc417f0c41bf53438a
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    switch(id) {
275    case BATTERY_PROP_CHARGE_COUNTER:
276        if (!mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
277            val->valueInt =
278                getIntField(mHealthdConfig->batteryChargeCounterPath);
279            ret = NO_ERROR;
280        } else {
281            ret = NAME_NOT_FOUND;
282        }
283        break;
284
285    case BATTERY_PROP_CURRENT_NOW:
286        if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
287            val->valueInt =
288                getIntField(mHealthdConfig->batteryCurrentNowPath);
289            ret = NO_ERROR;
290        } else {
291            ret = NAME_NOT_FOUND;
292        }
293        break;
294
295    case BATTERY_PROP_CURRENT_AVG:
296        if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
297            val->valueInt =
298                getIntField(mHealthdConfig->batteryCurrentAvgPath);
299            ret = NO_ERROR;
300        } else {
301            ret = NAME_NOT_FOUND;
302        }
303        break;
304
305    case BATTERY_PROP_CAPACITY:
306        if (!mHealthdConfig->batteryCapacityPath.isEmpty()) {
307            val->valueInt =
308                getIntField(mHealthdConfig->batteryCapacityPath);
309            ret = NO_ERROR;
310        } else {
311            ret = NAME_NOT_FOUND;
312        }
313        break;
314
315    default:
316        break;
317    }
318
319    if (ret != NO_ERROR)
320        val->valueInt = INT_MIN;
321
322    return ret;
323}
324
325void BatteryMonitor::dumpState(int fd) {
326    int v;
327    char vs[128];
328
329    snprintf(vs, sizeof(vs), "ac: %d usb: %d wireless: %d\n",
330             props.chargerAcOnline, props.chargerUsbOnline,
331             props.chargerWirelessOnline);
332    write(fd, vs, strlen(vs));
333    snprintf(vs, sizeof(vs), "status: %d health: %d present: %d\n",
334             props.batteryStatus, props.batteryHealth, props.batteryPresent);
335    write(fd, vs, strlen(vs));
336    snprintf(vs, sizeof(vs), "level: %d voltage: %d temp: %d\n",
337             props.batteryLevel, props.batteryVoltage,
338             props.batteryTemperature);
339    write(fd, vs, strlen(vs));
340
341    if (!mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
342        v = getIntField(mHealthdConfig->batteryCurrentNowPath);
343        snprintf(vs, sizeof(vs), "current now: %d\n", v);
344        write(fd, vs, strlen(vs));
345    }
346
347    if (!mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
348        v = getIntField(mHealthdConfig->batteryCurrentAvgPath);
349        snprintf(vs, sizeof(vs), "current avg: %d\n", v);
350        write(fd, vs, strlen(vs));
351    }
352
353    if (!mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
354        v = getIntField(mHealthdConfig->batteryChargeCounterPath);
355        snprintf(vs, sizeof(vs), "charge counter: %d\n", v);
356        write(fd, vs, strlen(vs));
357    }
358}
359
360void BatteryMonitor::init(struct healthd_config *hc) {
361    String8 path;
362
363    mHealthdConfig = hc;
364    DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
365    if (dir == NULL) {
366        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
367    } else {
368        struct dirent* entry;
369
370        while ((entry = readdir(dir))) {
371            const char* name = entry->d_name;
372
373            if (!strcmp(name, ".") || !strcmp(name, ".."))
374                continue;
375
376            char buf[20];
377            // Look for "type" file in each subdirectory
378            path.clear();
379            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);
380            switch(readPowerSupplyType(path)) {
381            case ANDROID_POWER_SUPPLY_TYPE_AC:
382            case ANDROID_POWER_SUPPLY_TYPE_USB:
383            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
384                path.clear();
385                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
386                if (access(path.string(), R_OK) == 0)
387                    mChargerNames.add(String8(name));
388                break;
389
390            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
391                mBatteryDevicePresent = true;
392
393                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
394                    path.clear();
395                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
396                                      name);
397                    if (access(path, R_OK) == 0)
398                        mHealthdConfig->batteryStatusPath = path;
399                }
400
401                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
402                    path.clear();
403                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
404                                      name);
405                    if (access(path, R_OK) == 0)
406                        mHealthdConfig->batteryHealthPath = path;
407                }
408
409                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
410                    path.clear();
411                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
412                                      name);
413                    if (access(path, R_OK) == 0)
414                        mHealthdConfig->batteryPresentPath = path;
415                }
416
417                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
418                    path.clear();
419                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
420                                      name);
421                    if (access(path, R_OK) == 0)
422                        mHealthdConfig->batteryCapacityPath = path;
423                }
424
425                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
426                    path.clear();
427                    path.appendFormat("%s/%s/voltage_now",
428                                      POWER_SUPPLY_SYSFS_PATH, name);
429                    if (access(path, R_OK) == 0) {
430                        mHealthdConfig->batteryVoltagePath = path;
431                    } else {
432                        path.clear();
433                        path.appendFormat("%s/%s/batt_vol",
434                                          POWER_SUPPLY_SYSFS_PATH, name);
435                        if (access(path, R_OK) == 0)
436                            mHealthdConfig->batteryVoltagePath = path;
437                    }
438                }
439
440                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
441                    path.clear();
442                    path.appendFormat("%s/%s/current_now",
443                                      POWER_SUPPLY_SYSFS_PATH, name);
444                    if (access(path, R_OK) == 0)
445                        mHealthdConfig->batteryCurrentNowPath = path;
446                }
447
448                if (mHealthdConfig->batteryCurrentAvgPath.isEmpty()) {
449                    path.clear();
450                    path.appendFormat("%s/%s/current_avg",
451                                      POWER_SUPPLY_SYSFS_PATH, name);
452                    if (access(path, R_OK) == 0)
453                        mHealthdConfig->batteryCurrentAvgPath = path;
454                }
455
456                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
457                    path.clear();
458                    path.appendFormat("%s/%s/charge_counter",
459                                      POWER_SUPPLY_SYSFS_PATH, name);
460                    if (access(path, R_OK) == 0)
461                        mHealthdConfig->batteryChargeCounterPath = path;
462                }
463
464                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
465                    path.clear();
466                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
467                                      name);
468                    if (access(path, R_OK) == 0) {
469                        mHealthdConfig->batteryTemperaturePath = path;
470                    } else {
471                        path.clear();
472                        path.appendFormat("%s/%s/batt_temp",
473                                          POWER_SUPPLY_SYSFS_PATH, name);
474                        if (access(path, R_OK) == 0)
475                            mHealthdConfig->batteryTemperaturePath = path;
476                    }
477                }
478
479                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
480                    path.clear();
481                    path.appendFormat("%s/%s/technology",
482                                      POWER_SUPPLY_SYSFS_PATH, name);
483                    if (access(path, R_OK) == 0)
484                        mHealthdConfig->batteryTechnologyPath = path;
485                }
486
487                break;
488
489            case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
490                break;
491            }
492        }
493        closedir(dir);
494    }
495
496    if (!mChargerNames.size())
497        KLOG_ERROR(LOG_TAG, "No charger supplies found\n");
498    if (!mBatteryDevicePresent) {
499        KLOG_INFO(LOG_TAG, "No battery devices found\n");
500        hc->periodic_chores_interval_fast = -1;
501        hc->periodic_chores_interval_slow = -1;
502    } else {
503        if (mHealthdConfig->batteryStatusPath.isEmpty())
504            KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
505        if (mHealthdConfig->batteryHealthPath.isEmpty())
506            KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
507        if (mHealthdConfig->batteryPresentPath.isEmpty())
508            KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
509        if (mHealthdConfig->batteryCapacityPath.isEmpty())
510            KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
511        if (mHealthdConfig->batteryVoltagePath.isEmpty())
512            KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
513        if (mHealthdConfig->batteryTemperaturePath.isEmpty())
514            KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
515        if (mHealthdConfig->batteryTechnologyPath.isEmpty())
516            KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
517    }
518}
519
520}; // namespace android
521