BatteryMonitor.cpp revision c133b7198a02c8738aeef86d6cfd921a626adce7
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    default:
301        break;
302    }
303
304    if (ret != NO_ERROR)
305        val->valueInt = INT_MIN;
306
307    return ret;
308}
309
310void BatteryMonitor::init(struct healthd_config *hc, bool nosvcmgr) {
311    String8 path;
312
313    mHealthdConfig = hc;
314    DIR* dir = opendir(POWER_SUPPLY_SYSFS_PATH);
315    if (dir == NULL) {
316        KLOG_ERROR(LOG_TAG, "Could not open %s\n", POWER_SUPPLY_SYSFS_PATH);
317    } else {
318        struct dirent* entry;
319
320        while ((entry = readdir(dir))) {
321            const char* name = entry->d_name;
322
323            if (!strcmp(name, ".") || !strcmp(name, ".."))
324                continue;
325
326            char buf[20];
327            // Look for "type" file in each subdirectory
328            path.clear();
329            path.appendFormat("%s/%s/type", POWER_SUPPLY_SYSFS_PATH, name);
330            switch(readPowerSupplyType(path)) {
331            case ANDROID_POWER_SUPPLY_TYPE_AC:
332            case ANDROID_POWER_SUPPLY_TYPE_USB:
333            case ANDROID_POWER_SUPPLY_TYPE_WIRELESS:
334                path.clear();
335                path.appendFormat("%s/%s/online", POWER_SUPPLY_SYSFS_PATH, name);
336                if (access(path.string(), R_OK) == 0)
337                    mChargerNames.add(String8(name));
338                break;
339
340            case ANDROID_POWER_SUPPLY_TYPE_BATTERY:
341                if (mHealthdConfig->batteryStatusPath.isEmpty()) {
342                    path.clear();
343                    path.appendFormat("%s/%s/status", POWER_SUPPLY_SYSFS_PATH,
344                                      name);
345                    if (access(path, R_OK) == 0)
346                        mHealthdConfig->batteryStatusPath = path;
347                }
348
349                if (mHealthdConfig->batteryHealthPath.isEmpty()) {
350                    path.clear();
351                    path.appendFormat("%s/%s/health", POWER_SUPPLY_SYSFS_PATH,
352                                      name);
353                    if (access(path, R_OK) == 0)
354                        mHealthdConfig->batteryHealthPath = path;
355                }
356
357                if (mHealthdConfig->batteryPresentPath.isEmpty()) {
358                    path.clear();
359                    path.appendFormat("%s/%s/present", POWER_SUPPLY_SYSFS_PATH,
360                                      name);
361                    if (access(path, R_OK) == 0)
362                        mHealthdConfig->batteryPresentPath = path;
363                }
364
365                if (mHealthdConfig->batteryCapacityPath.isEmpty()) {
366                    path.clear();
367                    path.appendFormat("%s/%s/capacity", POWER_SUPPLY_SYSFS_PATH,
368                                      name);
369                    if (access(path, R_OK) == 0)
370                        mHealthdConfig->batteryCapacityPath = path;
371                }
372
373                if (mHealthdConfig->batteryVoltagePath.isEmpty()) {
374                    path.clear();
375                    path.appendFormat("%s/%s/voltage_now",
376                                      POWER_SUPPLY_SYSFS_PATH, name);
377                    if (access(path, R_OK) == 0) {
378                        mHealthdConfig->batteryVoltagePath = path;
379                    } else {
380                        path.clear();
381                        path.appendFormat("%s/%s/batt_vol",
382                                          POWER_SUPPLY_SYSFS_PATH, name);
383                        if (access(path, R_OK) == 0)
384                            mHealthdConfig->batteryVoltagePath = path;
385                    }
386                }
387
388                if (mHealthdConfig->batteryCurrentNowPath.isEmpty()) {
389                    path.clear();
390                    path.appendFormat("%s/%s/current_now",
391                                      POWER_SUPPLY_SYSFS_PATH, name);
392                    if (access(path, R_OK) == 0)
393                        mHealthdConfig->batteryCurrentNowPath = path;
394                }
395
396                if (mHealthdConfig->batteryChargeCounterPath.isEmpty()) {
397                    path.clear();
398                    path.appendFormat("%s/%s/charge_counter",
399                                      POWER_SUPPLY_SYSFS_PATH, name);
400                    if (access(path, R_OK) == 0)
401                        mHealthdConfig->batteryChargeCounterPath = path;
402                }
403
404                if (mHealthdConfig->batteryTemperaturePath.isEmpty()) {
405                    path.clear();
406                    path.appendFormat("%s/%s/temp", POWER_SUPPLY_SYSFS_PATH,
407                                      name);
408                    if (access(path, R_OK) == 0) {
409                        mHealthdConfig->batteryTemperaturePath = path;
410                    } else {
411                        path.clear();
412                        path.appendFormat("%s/%s/batt_temp",
413                                          POWER_SUPPLY_SYSFS_PATH, name);
414                        if (access(path, R_OK) == 0)
415                            mHealthdConfig->batteryTemperaturePath = path;
416                    }
417                }
418
419                if (mHealthdConfig->batteryTechnologyPath.isEmpty()) {
420                    path.clear();
421                    path.appendFormat("%s/%s/technology",
422                                      POWER_SUPPLY_SYSFS_PATH, name);
423                    if (access(path, R_OK) == 0)
424                        mHealthdConfig->batteryTechnologyPath = path;
425                }
426
427                break;
428
429            case ANDROID_POWER_SUPPLY_TYPE_UNKNOWN:
430                break;
431            }
432        }
433        closedir(dir);
434    }
435
436    if (!mChargerNames.size())
437        KLOG_ERROR(LOG_TAG, "No charger supplies found\n");
438    if (mHealthdConfig->batteryStatusPath.isEmpty())
439        KLOG_WARNING(LOG_TAG, "BatteryStatusPath not found\n");
440    if (mHealthdConfig->batteryHealthPath.isEmpty())
441        KLOG_WARNING(LOG_TAG, "BatteryHealthPath not found\n");
442    if (mHealthdConfig->batteryPresentPath.isEmpty())
443        KLOG_WARNING(LOG_TAG, "BatteryPresentPath not found\n");
444    if (mHealthdConfig->batteryCapacityPath.isEmpty())
445        KLOG_WARNING(LOG_TAG, "BatteryCapacityPath not found\n");
446    if (mHealthdConfig->batteryVoltagePath.isEmpty())
447        KLOG_WARNING(LOG_TAG, "BatteryVoltagePath not found\n");
448    if (mHealthdConfig->batteryTemperaturePath.isEmpty())
449        KLOG_WARNING(LOG_TAG, "BatteryTemperaturePath not found\n");
450    if (mHealthdConfig->batteryTechnologyPath.isEmpty())
451        KLOG_WARNING(LOG_TAG, "BatteryTechnologyPath not found\n");
452
453    if (nosvcmgr == false) {
454            mBatteryPropertiesRegistrar = new BatteryPropertiesRegistrar(this);
455            mBatteryPropertiesRegistrar->publish();
456    }
457}
458
459}; // namespace android
460