summaryrefslogtreecommitdiff
path: root/hald/util_pm.c
blob: 26d8d9e62a83857ddc3d586d1acd045e65a1a799 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/***************************************************************************
 *
 * util_pm.c - Various power management related utilities that do not need
 *             to use HalDevice. This is suitable to use in addons and probers.
 *
 * Copyright (C) 2005 Richard Hughes <richard@hughsie.com>
 * Copyright (C) 2005 Danny Kukawka <danny.kukawka@web.de>
 *
 * Licensed under the Academic Free License version 2.1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 **************************************************************************/

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <stdint.h>

#include <glib.h>

#include "logger.h"

#include "util_pm.h"

typedef struct {
	int last_level;
	int last_chargeRate;
	int high_time_warn_count;
	time_t last_time;
} batteryInfo;

static GHashTable *saved_battery_info = NULL;

/**  
 *  util_get_battery_technology_
 *  @type:                The battery type recieved from the hardware
 *
 *  Returns:              The battery technology which is one of: unknown, lithium-ion or lead-acid
 *
 *  Convert the hardware reported value into a few sane choices
 *
 *  This is needed as ACPI does not specify the description text for a
 *  battery, and so we have to calculate it from the hardware output
 */
const char *
util_get_battery_technology (const char *type)
{
	if (type == NULL) {
		return "unknown";
	}
	/* every case combination of Li-Ion is commonly used.. */
	if (strcasecmp (type, "li-ion") == 0 ||
	    strcasecmp (type, "lion") == 0) {
		return "lithium-ion";
	}
	if (strcasecmp (type, "pb") == 0 ||
	    strcasecmp (type, "pbac") == 0) {
		return "lead-acid";
	}
	if (strcasecmp (type, "lip") == 0 ||
	    strcasecmp (type, "lipo") == 0) {
		return "lithium-polymer";
	}
	if (strcasecmp (type, "nimh") == 0) {
		return "nickel-metal-hydride";
	}
	if (strcasecmp (type, "lifo") == 0) {
		return "lithium-iron-phosphate";
	}
	return "unknown";
}

/** Given all the required parameters, this function will return the percentage
 *  charge remaining. There are lots of checks here as ACPI is often broken.
 *
 *  @@param  id                  Optional ID given to this battery. Unused at present.
 *  @@param  chargeLevel         The current charge level of the battery (typically mWh)
 *  @@param  chargeLastFull      The last "full" charge of the battery (typically mWh)
 *  @@return                     Percentage, -1 if invalid
 */
int 
util_compute_percentage_charge (const char *id,
			     int chargeLevel,
			     int chargeLastFull)
{
	int percentage;
	/* make sure we have chargelevel */
	if (chargeLevel <= 0) {
		HAL_WARNING (("chargeLevel %i, returning -1!", chargeLevel));
		return -1;
	}
	/* make sure not division by zero */
	if (chargeLastFull > 0)
		percentage = ((double) chargeLevel / (double) chargeLastFull) * 100;
	else {
		HAL_WARNING (("chargeLastFull %i, percentage returning -1!", chargeLastFull));
		return -1;
	}
	/* Some bios's will report this higher than 100, limit it here */
	if (percentage > 100) {
		HAL_WARNING (("Percentage %i, returning 100!", percentage));
		return 100;
	}
	/* Something really isn't right if we get a negative... */
	if (percentage < 0) {
		HAL_WARNING (("Percentage %i, returning -1!", percentage));
		return -1;
	}
	return percentage;
}

/**  
 *  util_compute_time_remaining:
 *  @id:                 Optional ID given to this battery. Unused at present.
 *  @chargeRate:         The "rate" (typically mW)
 *  @chargeLevel:        The current charge level of the battery (typically mWh)
 *  @chargeLastFull:     The last "full" charge of the battery (typically mWh)
 *  @isDischarging:      If battery is discharging
 *  @isCharging:         If battery is charging
 *  @guessChargeRate:    If ignore chargeRate and guess them.
 *
 *  Returns:                     Number of seconds, or -1 if invalid
 *
 *  Given all the required parameters, this function will return the number 
 *  of seconds until the battery is charged (if charging) or the number
 *  of seconds until empty (if discharging)
 */
int 
util_compute_time_remaining (const char *id,
			     int chargeRate,
			     int chargeLevel,
			     int chargeLastFull,
			     gboolean isDischarging,
			     gboolean isCharging,
			     gboolean guessChargeRate)
{
	int remaining_time = 0;

	/* should not get negative values */
	if (chargeRate < 0 || chargeLevel < 0 || chargeLastFull < 0) {
		HAL_WARNING (("chargeRate, chargeLevel or chargeLastFull < 0, returning -1"));
		return -1;
	}
	/* batteries cannot charge and discharge at the same time */
	if (isDischarging && isCharging) {
		HAL_WARNING (("isDischarging & isCharging TRUE, returning -1"));
		return -1;
	}
	/* 
	 * Some laptops don't supply any rate info, but that's no reason for HAL not
	 * to. We use the current and previous chargeLevel to estimate the rate.
	 * The info is stored in a GHashTable because there could be more than one battery.
	 */
	if (chargeRate == 0 || guessChargeRate) {
		batteryInfo *battery_info;
		time_t cur_time = time(NULL);

		/* Initialize the save_battery_info GHashTable */
		if (!saved_battery_info) 
			saved_battery_info = g_hash_table_new(g_str_hash, g_str_equal);

		if ((battery_info = g_hash_table_lookup(saved_battery_info, id))) {
			/* check this to prevent division by zero */
			if ((cur_time == battery_info->last_time) || (chargeLevel == battery_info->last_level)) {
				/* if we can't calculate because nothing changed, use last 
				 * chargeRate to prevent removing battery.remaining_time.
				 */
				chargeRate = battery_info->last_chargeRate;
			} else {
				chargeRate = ((chargeLevel - battery_info->last_level) * 60 * 60) / (cur_time - battery_info->last_time);
				/* During discharging chargeRate would be negative, which would mess 
				 * up the the calculation below, so we make sure it's always positive.
				 */ 
				chargeRate = (chargeRate > 0) ? chargeRate : -chargeRate;
	
				battery_info->last_level = chargeLevel;
				battery_info->last_time = cur_time;
				battery_info->last_chargeRate = chargeRate;
			}
		} else {
			battery_info = g_new0(batteryInfo, 1);
			g_hash_table_insert(saved_battery_info, (char*) id, battery_info);

			battery_info->last_level = chargeLevel;
			battery_info->last_time = cur_time;
			battery_info->last_chargeRate = 0;
 			battery_info->high_time_warn_count = 0;
			return -1;
		}
	} 

	if (chargeRate == 0)
		return -1;

	if (isDischarging) { 
		remaining_time = ((double) chargeLevel / (double) chargeRate) * 60 * 60;
	} else if (isCharging) {
		/* 
		 * Some ACPI BIOS's don't update chargeLastFull, 
		 * so return 0 as we don't know how much more there is left
		 */
		if (chargeLevel > chargeLastFull ) {
			HAL_WARNING (("chargeLevel > chargeLastFull, returning -1"));
			return -1;
		}
		remaining_time = ((double) (chargeLastFull - chargeLevel) / (double) chargeRate) * 60 * 60;
	}
	
	/* This shouldn't happen, but check for completeness */
	if (remaining_time < 0) {
		HAL_WARNING (("remaining_time %i, returning -1", remaining_time));
		remaining_time = -1;
	}
	/* Battery life cannot be above 60 hours */
	else if (remaining_time > 60*60*60) {
		batteryInfo *battery_info;

		/* to be sure saved_battery_info is initialised */
		if (!saved_battery_info) 
			saved_battery_info = g_hash_table_new(g_str_hash, g_str_equal);

		if (!(battery_info = g_hash_table_lookup(saved_battery_info, id))) {
			battery_info = g_new0(batteryInfo, 1);
			g_hash_table_insert(saved_battery_info, (char*) id, battery_info);
			battery_info->last_level = -1;
			battery_info->last_time = -1;
			battery_info->last_chargeRate = -1;
			battery_info->high_time_warn_count = 0;
		}

		/* display the warning only 10 times and then ever 100 calls , should  avoid to flood syslog */
		if (battery_info->high_time_warn_count < 10 || !(battery_info->high_time_warn_count % 100)) {
			HAL_WARNING (("remaining_time *very* high, returning -1"));
			battery_info->high_time_warn_count += 1;
		}

		remaining_time = -1;
	}

	return remaining_time;
}