mirror of
https://gitlab.freedesktop.org/upower/upower.git
synced 2026-05-09 08:18:08 +02:00
Use linear regression to get better predicted battery times
For hardware that has no rate data we use the differences in charge over a time period to work out the effective rate. Using linear regression this estimation is much more realistic. To do this, introduce a simple circular buffer and use an index to calculate the rate on each update. Signed-off-by: Richard Hughes <richard@hughsie.com>
This commit is contained in:
parent
58f77e8aa6
commit
b971b43c45
1 changed files with 104 additions and 29 deletions
|
|
@ -45,13 +45,17 @@
|
|||
#define UP_DEVICE_SUPPLY_COLDPLUG_UNITS_CHARGE TRUE
|
||||
#define UP_DEVICE_SUPPLY_COLDPLUG_UNITS_ENERGY FALSE
|
||||
|
||||
/* number of old energy values to keep cached */
|
||||
#define UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH 4
|
||||
|
||||
struct UpDeviceSupplyPrivate
|
||||
{
|
||||
guint poll_timer_id;
|
||||
gboolean has_coldplug_values;
|
||||
gboolean coldplug_units;
|
||||
gdouble energy_old;
|
||||
GTimeVal energy_old_timespec;
|
||||
gdouble *energy_old;
|
||||
GTimeVal *energy_old_timespec;
|
||||
guint energy_old_first;
|
||||
gdouble rate_old;
|
||||
guint unknown_retries;
|
||||
gboolean enable_poll;
|
||||
|
|
@ -118,12 +122,17 @@ static void
|
|||
up_device_supply_reset_values (UpDeviceSupply *supply)
|
||||
{
|
||||
UpDevice *device = UP_DEVICE (supply);
|
||||
guint i;
|
||||
|
||||
supply->priv->has_coldplug_values = FALSE;
|
||||
supply->priv->coldplug_units = UP_DEVICE_SUPPLY_COLDPLUG_UNITS_ENERGY;
|
||||
supply->priv->rate_old = 0;
|
||||
supply->priv->energy_old = 0;
|
||||
supply->priv->energy_old_timespec.tv_sec = 0;
|
||||
|
||||
for (i = 0; i < UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH; ++i) {
|
||||
supply->priv->energy_old[i] = 0.0f;
|
||||
supply->priv->energy_old_timespec[i].tv_sec = 0;
|
||||
}
|
||||
supply->priv->energy_old_first = 0;
|
||||
|
||||
/* reset to default */
|
||||
g_object_set (device,
|
||||
|
|
@ -239,37 +248,91 @@ up_device_supply_get_online (UpDevice *device, gboolean *online)
|
|||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* up_device_supply_push_new_energy:
|
||||
*
|
||||
* Store the new energy in the list of old energies of the supply, so
|
||||
* it can be used to determine the energy rate.
|
||||
*/
|
||||
static gboolean
|
||||
up_device_supply_push_new_energy (UpDeviceSupply *supply, gdouble energy)
|
||||
{
|
||||
guint first = supply->priv->energy_old_first;
|
||||
guint new_position = (first + UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH - 1) %
|
||||
UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH;
|
||||
|
||||
/* check if the energy value has changed and, if that's the case,
|
||||
* store the new values in the buffer. */
|
||||
if (supply->priv->energy_old[first] != energy) {
|
||||
supply->priv->energy_old[new_position] = energy;
|
||||
g_get_current_time (&supply->priv->energy_old_timespec[new_position]);
|
||||
supply->priv->energy_old_first = new_position;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* up_device_supply_calculate_rate:
|
||||
**/
|
||||
static gdouble
|
||||
up_device_supply_calculate_rate (UpDeviceSupply *supply, gdouble energy)
|
||||
{
|
||||
guint time_s;
|
||||
gdouble rate = 0.0f;
|
||||
gdouble sum_x = 0.0f; /* sum of the squared times difference */
|
||||
GTimeVal now;
|
||||
guint i;
|
||||
guint valid_values = 0;
|
||||
|
||||
if (energy < 0.1f)
|
||||
return 0.0f;
|
||||
|
||||
if (supply->priv->energy_old < 0.1f)
|
||||
return 0.0f;
|
||||
|
||||
if (supply->priv->energy_old - energy < 0.01)
|
||||
return supply->priv->rate_old;
|
||||
|
||||
/* get the time difference */
|
||||
/* get the time difference from now and use linear regression to determine
|
||||
* the discharge rate of the battery. */
|
||||
g_get_current_time (&now);
|
||||
time_s = now.tv_sec - supply->priv->energy_old_timespec.tv_sec;
|
||||
if (time_s == 0)
|
||||
return supply->priv->rate_old;
|
||||
|
||||
/* get the difference in charge */
|
||||
energy = fabs (supply->priv->energy_old - energy);
|
||||
/* store the data on the new energy received */
|
||||
up_device_supply_push_new_energy (supply, energy);
|
||||
|
||||
if (energy < 0.1f)
|
||||
return 0.0f;
|
||||
|
||||
if (supply->priv->energy_old[supply->priv->energy_old_first] < 0.1f)
|
||||
return 0.0f;
|
||||
|
||||
/* don't use the new point obtained since it may cause instability in
|
||||
* the estimate */
|
||||
i = supply->priv->energy_old_first;
|
||||
now = supply->priv->energy_old_timespec[i];
|
||||
do {
|
||||
/* only use this value if it seems valid */
|
||||
if (supply->priv->energy_old_timespec[i].tv_sec && supply->priv->energy_old[i]) {
|
||||
/* This is the square of t_i^2 */
|
||||
sum_x += (now.tv_sec - supply->priv->energy_old_timespec[i].tv_sec) *
|
||||
(now.tv_sec - supply->priv->energy_old_timespec[i].tv_sec);
|
||||
|
||||
/* Sum the module of the energy difference */
|
||||
rate += fabs ((supply->priv->energy_old_timespec[i].tv_sec - now.tv_sec) *
|
||||
(energy - supply->priv->energy_old[i]));
|
||||
valid_values++;
|
||||
}
|
||||
|
||||
/* get the next element in the circular buffer */
|
||||
i = (i + 1) % UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH;
|
||||
} while (i != supply->priv->energy_old_first);
|
||||
|
||||
/* Check that at least 3 points were involved in computation */
|
||||
if (sum_x == 0.0f || valid_values < 3)
|
||||
return supply->priv->rate_old;
|
||||
|
||||
/* probably okay */
|
||||
return energy * 3600.0 / time_s;
|
||||
/* Compute the discharge per hour, and not per second */
|
||||
rate /= sum_x / 3600.0f;
|
||||
|
||||
/* if the rate is zero, use the old rate. It will usually happens if no
|
||||
* data is in the buffer yet. If the rate is too high, i.e. more than,
|
||||
* 100W don't use it. */
|
||||
if (rate == 0.0f || rate > 100.0f)
|
||||
return supply->priv->rate_old;
|
||||
|
||||
return rate;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -719,17 +782,21 @@ up_device_supply_refresh_battery (UpDeviceSupply *supply)
|
|||
if (time_to_full > (20 * 60 * 60))
|
||||
time_to_full = 0;
|
||||
|
||||
/* set the old status if it hasn't changed */
|
||||
if (supply->priv->energy_old != energy) {
|
||||
supply->priv->energy_old = energy;
|
||||
/* check if the energy value has changed and, if that's the case,
|
||||
* store the new values in the buffer. */
|
||||
if (up_device_supply_push_new_energy (supply, energy))
|
||||
supply->priv->rate_old = energy_rate;
|
||||
g_get_current_time (&supply->priv->energy_old_timespec);
|
||||
}
|
||||
|
||||
/* we changed state */
|
||||
g_object_get (device, "state", &old_state, NULL);
|
||||
if (old_state != state)
|
||||
supply->priv->energy_old = 0;
|
||||
if (old_state != state) {
|
||||
for (i = 0; i < UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH; ++i) {
|
||||
supply->priv->energy_old[i] = 0.0f;
|
||||
supply->priv->energy_old_timespec[i].tv_sec = 0;
|
||||
|
||||
}
|
||||
supply->priv->energy_old_first = 0;
|
||||
}
|
||||
|
||||
g_object_set (device,
|
||||
"energy", energy,
|
||||
|
|
@ -919,6 +986,11 @@ up_device_supply_init (UpDeviceSupply *supply)
|
|||
supply->priv->unknown_retries = 0;
|
||||
supply->priv->poll_timer_id = 0;
|
||||
supply->priv->enable_poll = TRUE;
|
||||
|
||||
/* allocate the stats for the battery charging & discharging */
|
||||
supply->priv->energy_old = g_new (gdouble, UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH);
|
||||
supply->priv->energy_old_timespec = g_new (GTimeVal, UP_DEVICE_SUPPLY_ENERGY_OLD_LENGTH);
|
||||
supply->priv->energy_old_first = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -938,6 +1010,9 @@ up_device_supply_finalize (GObject *object)
|
|||
if (supply->priv->poll_timer_id > 0)
|
||||
g_source_remove (supply->priv->poll_timer_id);
|
||||
|
||||
g_free (supply->priv->energy_old);
|
||||
g_free (supply->priv->energy_old_timespec);
|
||||
|
||||
G_OBJECT_CLASS (up_device_supply_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue