mirror of
https://gitlab.freedesktop.org/upower/upower.git
synced 2026-06-02 06:08:20 +02:00
902 lines
24 KiB
C
902 lines
24 KiB
C
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
|
|
*
|
|
* Copyright (C) 2008 David Zeuthen <davidz@redhat.com>
|
|
* Copyright (C) 2008 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#include <glib.h>
|
|
#include <glib/gstdio.h>
|
|
#include <glib/gprintf.h>
|
|
#include <glib/gi18n-lib.h>
|
|
#include <glib-object.h>
|
|
#include <gudev/gudev.h>
|
|
|
|
#include "sysfs-utils.h"
|
|
#include "egg-debug.h"
|
|
|
|
#include "dkp-enum.h"
|
|
#include "dkp-device-supply.h"
|
|
|
|
#define DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT 30 /* seconds */
|
|
#define DKP_DEVICE_SUPPLY_UNKNOWN_TIMEOUT 2 /* seconds */
|
|
#define DKP_DEVICE_SUPPLY_UNKNOWN_RETRIES 30
|
|
#define DKP_DEVICE_SUPPLY_CHARGED_THRESHOLD 90.0f /* % */
|
|
|
|
struct DkpDeviceSupplyPrivate
|
|
{
|
|
guint poll_timer_id;
|
|
gboolean has_coldplug_values;
|
|
gdouble energy_old;
|
|
GTimeVal energy_old_timespec;
|
|
guint unknown_retries;
|
|
gboolean enable_poll;
|
|
};
|
|
|
|
G_DEFINE_TYPE (DkpDeviceSupply, dkp_device_supply, DKP_TYPE_DEVICE)
|
|
#define DKP_DEVICE_SUPPLY_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DKP_TYPE_SUPPLY, DkpDeviceSupplyPrivate))
|
|
|
|
static gboolean dkp_device_supply_refresh (DkpDevice *device);
|
|
|
|
/**
|
|
* dkp_device_supply_refresh_line_power:
|
|
*
|
|
* Return %TRUE on success, %FALSE if we failed to refresh or no data
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_refresh_line_power (DkpDeviceSupply *supply)
|
|
{
|
|
DkpDevice *device = DKP_DEVICE (supply);
|
|
GUdevDevice *native;
|
|
const gchar *native_path;
|
|
|
|
/* force true */
|
|
g_object_set (device, "power-supply", TRUE, NULL);
|
|
|
|
/* get new AC value */
|
|
native = G_UDEV_DEVICE (dkp_device_get_native (device));
|
|
native_path = g_udev_device_get_sysfs_path (native);
|
|
g_object_set (device, "online", sysfs_get_int (native_path, "online"), NULL);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_reset_values:
|
|
**/
|
|
static void
|
|
dkp_device_supply_reset_values (DkpDeviceSupply *supply)
|
|
{
|
|
DkpDevice *device = DKP_DEVICE (supply);
|
|
|
|
supply->priv->has_coldplug_values = FALSE;
|
|
supply->priv->energy_old = 0;
|
|
supply->priv->energy_old_timespec.tv_sec = 0;
|
|
|
|
/* reset to default */
|
|
g_object_set (device,
|
|
"vendor", NULL,
|
|
"model", NULL,
|
|
"serial", NULL,
|
|
"update-time", 0,
|
|
"power-supply", FALSE,
|
|
"online", FALSE,
|
|
"energy", 0.0,
|
|
"is-present", FALSE,
|
|
"is-rechargeable", FALSE,
|
|
"has-history", FALSE,
|
|
"has-statistics", FALSE,
|
|
"state", NULL,
|
|
"capacity", 0.0,
|
|
"energy-empty", 0.0,
|
|
"energy-full", 0.0,
|
|
"energy-full-design", 0.0,
|
|
"energy-rate", 0.0,
|
|
"voltage", 0.0,
|
|
"time-to-empty", 0,
|
|
"time-to-full", 0,
|
|
"percentage", 0,
|
|
"technology", NULL,
|
|
NULL);
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_get_on_battery:
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_get_on_battery (DkpDevice *device, gboolean *on_battery)
|
|
{
|
|
DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
|
|
DkpDeviceType type;
|
|
DkpDeviceState state;
|
|
gboolean is_present;
|
|
|
|
g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE);
|
|
g_return_val_if_fail (on_battery != NULL, FALSE);
|
|
|
|
g_object_get (device,
|
|
"type", &type,
|
|
"state", &state,
|
|
"is-present", &is_present,
|
|
NULL);
|
|
|
|
if (type != DKP_DEVICE_TYPE_BATTERY)
|
|
return FALSE;
|
|
if (state == DKP_DEVICE_STATE_UNKNOWN)
|
|
return FALSE;
|
|
if (!is_present)
|
|
return FALSE;
|
|
|
|
*on_battery = (state == DKP_DEVICE_STATE_DISCHARGING);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_get_low_battery:
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_get_low_battery (DkpDevice *device, gboolean *low_battery)
|
|
{
|
|
gboolean ret;
|
|
gboolean on_battery;
|
|
DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
|
|
gdouble percentage;
|
|
|
|
g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE);
|
|
g_return_val_if_fail (low_battery != NULL, FALSE);
|
|
|
|
/* reuse the common checks */
|
|
ret = dkp_device_supply_get_on_battery (device, &on_battery);
|
|
if (!ret)
|
|
return FALSE;
|
|
|
|
/* shortcut */
|
|
if (!on_battery) {
|
|
*low_battery = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
g_object_get (device, "percentage", &percentage, NULL);
|
|
*low_battery = (percentage < 10.0f);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_get_online:
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_get_online (DkpDevice *device, gboolean *online)
|
|
{
|
|
DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
|
|
DkpDeviceType type;
|
|
gboolean online_tmp;
|
|
|
|
g_return_val_if_fail (DKP_IS_SUPPLY (supply), FALSE);
|
|
g_return_val_if_fail (online != NULL, FALSE);
|
|
|
|
g_object_get (device,
|
|
"type", &type,
|
|
"online", &online_tmp,
|
|
NULL);
|
|
|
|
if (type != DKP_DEVICE_TYPE_LINE_POWER)
|
|
return FALSE;
|
|
|
|
*online = online_tmp;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_calculate_rate:
|
|
**/
|
|
static void
|
|
dkp_device_supply_calculate_rate (DkpDeviceSupply *supply)
|
|
{
|
|
guint time_s;
|
|
gdouble energy;
|
|
gdouble energy_rate;
|
|
GTimeVal now;
|
|
DkpDevice *device = DKP_DEVICE (supply);
|
|
|
|
g_object_get (device, "energy", &energy, NULL);
|
|
|
|
if (energy < 0.1f)
|
|
return;
|
|
|
|
if (supply->priv->energy_old < 0.1f)
|
|
return;
|
|
|
|
if (supply->priv->energy_old == energy)
|
|
return;
|
|
|
|
/* get the time difference */
|
|
g_get_current_time (&now);
|
|
time_s = now.tv_sec - supply->priv->energy_old_timespec.tv_sec;
|
|
|
|
if (time_s == 0)
|
|
return;
|
|
|
|
/* get the difference in charge */
|
|
energy = supply->priv->energy_old - energy;
|
|
if (energy < 0.1f)
|
|
return;
|
|
|
|
/* probably okay */
|
|
energy_rate = energy * 3600 / time_s;
|
|
g_object_set (device, "energy-rate", energy_rate, NULL);
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_convert_device_technology:
|
|
**/
|
|
static DkpDeviceTechnology
|
|
dkp_device_supply_convert_device_technology (const gchar *type)
|
|
{
|
|
if (type == NULL)
|
|
return DKP_DEVICE_TECHNOLOGY_UNKNOWN;
|
|
/* every case combination of Li-Ion is commonly used.. */
|
|
if (g_ascii_strcasecmp (type, "li-ion") == 0 ||
|
|
g_ascii_strcasecmp (type, "lion") == 0)
|
|
return DKP_DEVICE_TECHNOLOGY_LITHIUM_ION;
|
|
if (g_ascii_strcasecmp (type, "pb") == 0 ||
|
|
g_ascii_strcasecmp (type, "pbac") == 0)
|
|
return DKP_DEVICE_TECHNOLOGY_LEAD_ACID;
|
|
if (g_ascii_strcasecmp (type, "lip") == 0 ||
|
|
g_ascii_strcasecmp (type, "lipo") == 0 ||
|
|
g_ascii_strcasecmp (type, "li-poly") == 0)
|
|
return DKP_DEVICE_TECHNOLOGY_LITHIUM_POLYMER;
|
|
if (g_ascii_strcasecmp (type, "nimh") == 0)
|
|
return DKP_DEVICE_TECHNOLOGY_NICKEL_METAL_HYDRIDE;
|
|
if (g_ascii_strcasecmp (type, "lifo") == 0)
|
|
return DKP_DEVICE_TECHNOLOGY_LITHIUM_IRON_PHOSPHATE;
|
|
return DKP_DEVICE_TECHNOLOGY_UNKNOWN;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_get_string:
|
|
**/
|
|
static gchar *
|
|
dkp_device_supply_get_string (const gchar *native_path, const gchar *key)
|
|
{
|
|
gchar *value;
|
|
|
|
/* get value, and strip to remove spaces */
|
|
value = g_strstrip (sysfs_get_string (native_path, key));
|
|
|
|
/* no value */
|
|
if (value == NULL)
|
|
goto out;
|
|
|
|
/* empty value */
|
|
if (value[0] == '\0') {
|
|
g_free (value);
|
|
value = NULL;
|
|
goto out;
|
|
}
|
|
out:
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_get_design_voltage:
|
|
**/
|
|
static gdouble
|
|
dkp_device_supply_get_design_voltage (const gchar *native_path)
|
|
{
|
|
gdouble voltage;
|
|
|
|
/* design maximum */
|
|
voltage = sysfs_get_double (native_path, "voltage_max_design") / 1000000.0;
|
|
if (voltage > 1.00f) {
|
|
egg_debug ("using max design voltage");
|
|
goto out;
|
|
}
|
|
|
|
/* design minimum */
|
|
voltage = sysfs_get_double (native_path, "voltage_min_design") / 1000000.0;
|
|
if (voltage > 1.00f) {
|
|
egg_debug ("using min design voltage");
|
|
goto out;
|
|
}
|
|
|
|
/* current voltage */
|
|
voltage = sysfs_get_double (native_path, "voltage_present") / 1000000.0;
|
|
if (voltage > 1.00f) {
|
|
egg_debug ("using present voltage");
|
|
goto out;
|
|
}
|
|
|
|
/* current voltage, alternate form */
|
|
voltage = sysfs_get_double (native_path, "voltage_now") / 1000000.0;
|
|
if (voltage > 1.00f) {
|
|
egg_debug ("using present voltage (alternate)");
|
|
goto out;
|
|
}
|
|
|
|
/* completely guess, to avoid getting zero values */
|
|
egg_warning ("no voltage values, using 10V as approximation");
|
|
voltage = 10.0f;
|
|
out:
|
|
return voltage;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_make_safe_string:
|
|
**/
|
|
static void
|
|
dkp_device_supply_make_safe_string (gchar *text)
|
|
{
|
|
guint i;
|
|
guint idx = 0;
|
|
|
|
/* no point checking */
|
|
if (text == NULL)
|
|
return;
|
|
|
|
/* shunt up only safe chars */
|
|
for (i=0; text[i] != '\0'; i++) {
|
|
if (g_ascii_isprint (text[i])) {
|
|
/* only copy if the address is going to change */
|
|
if (idx != i)
|
|
text[idx] = text[i];
|
|
idx++;
|
|
} else {
|
|
egg_debug ("invalid char '%c'", text[i]);
|
|
}
|
|
}
|
|
|
|
/* ensure null terminated */
|
|
text[idx] = '\0';
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_refresh_battery:
|
|
*
|
|
* Return %TRUE on success, %FALSE if we failed to refresh or no data
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_refresh_battery (DkpDeviceSupply *supply)
|
|
{
|
|
gchar *status = NULL;
|
|
gchar *technology_native = NULL;
|
|
gboolean ret = TRUE;
|
|
gdouble voltage_design;
|
|
DkpDeviceState old_state;
|
|
DkpDeviceState state;
|
|
DkpDevice *device = DKP_DEVICE (supply);
|
|
const gchar *native_path;
|
|
GUdevDevice *native;
|
|
gboolean is_present;
|
|
gdouble energy;
|
|
gdouble energy_full;
|
|
gdouble energy_full_design;
|
|
gdouble energy_rate;
|
|
gdouble capacity;
|
|
gdouble percentage = 0.0f;
|
|
gdouble voltage;
|
|
guint64 time_to_empty;
|
|
guint64 time_to_full;
|
|
gchar *manufacturer = NULL;
|
|
gchar *model_name = NULL;
|
|
gchar *serial_number = NULL;
|
|
gboolean recall_notice;
|
|
const gchar *recall_vendor = NULL;
|
|
const gchar *recall_url = NULL;
|
|
DkpDaemon *daemon;
|
|
gboolean on_battery;
|
|
guint battery_count;
|
|
|
|
native = G_UDEV_DEVICE (dkp_device_get_native (device));
|
|
native_path = g_udev_device_get_sysfs_path (native);
|
|
|
|
/* have we just been removed? */
|
|
is_present = sysfs_get_bool (native_path, "present");
|
|
g_object_set (device, "is-present", is_present, NULL);
|
|
if (!is_present) {
|
|
dkp_device_supply_reset_values (supply);
|
|
goto out;
|
|
}
|
|
|
|
/* get the currect charge */
|
|
energy = sysfs_get_double (native_path, "energy_now") / 1000000.0;
|
|
if (energy == 0)
|
|
energy = sysfs_get_double (native_path, "energy_avg") / 1000000.0;
|
|
|
|
/* used to convert A to W later */
|
|
voltage_design = dkp_device_supply_get_design_voltage (native_path);
|
|
|
|
/* initial values */
|
|
if (!supply->priv->has_coldplug_values) {
|
|
|
|
/* when we add via sysfs power_supply class then we know this is true */
|
|
g_object_set (device, "power-supply", TRUE, NULL);
|
|
|
|
/* the ACPI spec is bad at defining battery type constants */
|
|
technology_native = dkp_device_supply_get_string (native_path, "technology");
|
|
g_object_set (device, "technology", dkp_device_supply_convert_device_technology (technology_native), NULL);
|
|
|
|
/* get values which may be blank */
|
|
manufacturer = dkp_device_supply_get_string (native_path, "manufacturer");
|
|
model_name = dkp_device_supply_get_string (native_path, "model_name");
|
|
serial_number = dkp_device_supply_get_string (native_path, "serial_number");
|
|
|
|
/* some vendors fill this with binary garbage */
|
|
dkp_device_supply_make_safe_string (manufacturer);
|
|
dkp_device_supply_make_safe_string (model_name);
|
|
dkp_device_supply_make_safe_string (serial_number);
|
|
|
|
/* are we possibly recalled by the vendor? */
|
|
recall_notice = g_udev_device_has_property (native, "DKP_RECALL_NOTICE");
|
|
if (recall_notice) {
|
|
recall_vendor = g_udev_device_get_property (native, "DKP_RECALL_VENDOR");
|
|
recall_url = g_udev_device_get_property (native, "DKP_RECALL_URL");
|
|
}
|
|
|
|
g_object_set (device,
|
|
"vendor", manufacturer,
|
|
"model", model_name,
|
|
"serial", serial_number,
|
|
"is-rechargeable", TRUE, /* assume true for laptops */
|
|
"has-history", TRUE,
|
|
"has-statistics", TRUE,
|
|
"recall-notice", recall_notice,
|
|
"recall-vendor", recall_vendor,
|
|
"recall-url", recall_url,
|
|
NULL);
|
|
|
|
/* these don't change at runtime */
|
|
energy_full = sysfs_get_double (native_path, "energy_full") / 1000000.0;
|
|
energy_full_design = sysfs_get_double (native_path, "energy_full_design") / 1000000.0;
|
|
|
|
/* convert charge to energy */
|
|
if (energy == 0) {
|
|
energy_full = sysfs_get_double (native_path, "charge_full") / 1000000.0;
|
|
energy_full_design = sysfs_get_double (native_path, "charge_full_design") / 1000000.0;
|
|
energy_full *= voltage_design;
|
|
energy_full_design *= voltage_design;
|
|
}
|
|
|
|
/* the last full should not be bigger than the design */
|
|
if (energy_full > energy_full_design)
|
|
egg_warning ("energy_full (%f) is greater than energy_full_design (%f)",
|
|
energy_full, energy_full_design);
|
|
|
|
/* some systems don't have this */
|
|
if (energy_full < 0.01) {
|
|
egg_warning ("correcting energy_full (%f) using energy_full_design (%f)",
|
|
energy_full, energy_full_design);
|
|
energy_full = energy_full_design;
|
|
}
|
|
|
|
/* calculate how broken our battery is */
|
|
if (energy_full > 0) {
|
|
capacity = (energy_full / energy_full_design) * 100.0f;
|
|
if (capacity < 0)
|
|
capacity = 0.0;
|
|
if (capacity > 100.0)
|
|
capacity = 100.0;
|
|
}
|
|
g_object_set (device, "capacity", capacity, NULL);
|
|
|
|
/* we only coldplug once, as these values will never change */
|
|
supply->priv->has_coldplug_values = TRUE;
|
|
} else {
|
|
/* get the old full */
|
|
g_object_get (device,
|
|
"energy-full", &energy_full,
|
|
"energy-full-design", &energy_full_design,
|
|
NULL);
|
|
}
|
|
|
|
status = g_strstrip (sysfs_get_string (native_path, "status"));
|
|
if (g_ascii_strcasecmp (status, "charging") == 0)
|
|
state = DKP_DEVICE_STATE_CHARGING;
|
|
else if (g_ascii_strcasecmp (status, "discharging") == 0)
|
|
state = DKP_DEVICE_STATE_DISCHARGING;
|
|
else if (g_ascii_strcasecmp (status, "full") == 0)
|
|
state = DKP_DEVICE_STATE_FULLY_CHARGED;
|
|
else if (g_ascii_strcasecmp (status, "empty") == 0)
|
|
state = DKP_DEVICE_STATE_EMPTY;
|
|
else if (g_ascii_strcasecmp (status, "unknown") == 0)
|
|
state = DKP_DEVICE_STATE_UNKNOWN;
|
|
else {
|
|
egg_warning ("unknown status string: %s", status);
|
|
state = DKP_DEVICE_STATE_UNKNOWN;
|
|
}
|
|
|
|
/* only disable the polling if the kernel tells us we're fully charged,
|
|
not if we've guessed the state to be fully charged */
|
|
supply->priv->enable_poll = (state != DKP_DEVICE_STATE_FULLY_CHARGED);
|
|
|
|
/* reset unknown counter */
|
|
if (state != DKP_DEVICE_STATE_UNKNOWN) {
|
|
egg_debug ("resetting unknown timeout after %i retries", supply->priv->unknown_retries);
|
|
supply->priv->unknown_retries = 0;
|
|
}
|
|
|
|
/* get rate; it seems odd as it's either in uVh or uWh */
|
|
energy_rate = fabs (sysfs_get_double (native_path, "current_now") / 1000000.0);
|
|
|
|
/* convert charge to energy */
|
|
if (energy == 0) {
|
|
energy = sysfs_get_double (native_path, "charge_now") / 1000000.0;
|
|
if (energy == 0)
|
|
energy = sysfs_get_double (native_path, "charge_avg") / 1000000.0;
|
|
energy *= voltage_design;
|
|
energy_rate *= voltage_design;
|
|
}
|
|
|
|
/* some batteries don't update last_full attribute */
|
|
if (energy > energy_full) {
|
|
egg_warning ("energy %f bigger than full %f", energy, energy_full);
|
|
energy_full = energy;
|
|
}
|
|
|
|
/* present voltage */
|
|
voltage = sysfs_get_double (native_path, "voltage_now") / 1000000.0;
|
|
if (voltage == 0)
|
|
voltage = sysfs_get_double (native_path, "voltage_avg") / 1000000.0;
|
|
|
|
/* ACPI gives out the special 'Ones' value for rate when it's unable
|
|
* to calculate the true rate. We should set the rate zero, and wait
|
|
* for the BIOS to stabilise. */
|
|
if (energy_rate == 0xffff)
|
|
energy_rate = 0;
|
|
|
|
/* sanity check to less than 100W */
|
|
if (energy_rate > 100*1000)
|
|
energy_rate = 0;
|
|
|
|
/* the hardware reporting failed -- try to calculate this */
|
|
if (energy_rate < 0)
|
|
dkp_device_supply_calculate_rate (supply);
|
|
|
|
/* get a precise percentage */
|
|
if (energy_full > 0.0f) {
|
|
percentage = 100.0 * energy / energy_full;
|
|
if (percentage < 0.0f)
|
|
percentage = 0.0f;
|
|
if (percentage > 100.0f)
|
|
percentage = 100.0f;
|
|
}
|
|
|
|
/* some batteries stop charging much before 100% */
|
|
if (state == DKP_DEVICE_STATE_UNKNOWN &&
|
|
percentage > DKP_DEVICE_SUPPLY_CHARGED_THRESHOLD) {
|
|
egg_debug ("fixing up unknown %f", percentage);
|
|
state = DKP_DEVICE_STATE_FULLY_CHARGED;
|
|
}
|
|
|
|
/* the battery isn't charging or discharging, it's just
|
|
* sitting there half full doing nothing: try to guess a state */
|
|
if (state == DKP_DEVICE_STATE_UNKNOWN) {
|
|
|
|
/* get global battery status */
|
|
daemon = dkp_device_get_daemon (device);
|
|
g_object_get (daemon,
|
|
"on-battery", &on_battery,
|
|
NULL);
|
|
|
|
/* only guess when we have more than one battery devices */
|
|
battery_count = dkp_daemon_get_number_devices_of_type (daemon, DKP_DEVICE_TYPE_BATTERY);
|
|
|
|
/* try to find a suitable icon depending on AC state */
|
|
if (battery_count > 1) {
|
|
if (on_battery && percentage < 1.0f) {
|
|
/* battery is low */
|
|
state = DKP_DEVICE_STATE_EMPTY;
|
|
} else if (on_battery) {
|
|
/* battery is waiting */
|
|
state = DKP_DEVICE_STATE_PENDING_DISCHARGE;
|
|
} else {
|
|
/* battery is waiting */
|
|
state = DKP_DEVICE_STATE_PENDING_CHARGE;
|
|
}
|
|
} else {
|
|
if (on_battery) {
|
|
/* battery is assumed discharging */
|
|
state = DKP_DEVICE_STATE_DISCHARGING;
|
|
} else {
|
|
/* battery is waiting */
|
|
state = DKP_DEVICE_STATE_FULLY_CHARGED;
|
|
}
|
|
}
|
|
|
|
/* print what we did */
|
|
egg_debug ("guessing battery state '%s' using global on-battery:%i",
|
|
dkp_device_state_to_text (state), on_battery);
|
|
|
|
g_object_unref (daemon);
|
|
}
|
|
|
|
/* if empty, and BIOS does not know what to do */
|
|
if (state == DKP_DEVICE_STATE_UNKNOWN && energy < 0.01) {
|
|
egg_warning ("Setting %s state empty as unknown and very low", native_path);
|
|
state = DKP_DEVICE_STATE_EMPTY;
|
|
}
|
|
|
|
/* some batteries give out massive rate values when nearly empty */
|
|
if (energy < 0.1f)
|
|
energy_rate = 0.0f;
|
|
|
|
/* calculate a quick and dirty time remaining value */
|
|
time_to_empty = 0;
|
|
time_to_full = 0;
|
|
if (energy_rate > 0) {
|
|
if (state == DKP_DEVICE_STATE_DISCHARGING)
|
|
time_to_empty = 3600 * (energy / energy_rate);
|
|
else if (state == DKP_DEVICE_STATE_CHARGING)
|
|
time_to_full = 3600 * ((energy_full - energy) / energy_rate);
|
|
/* TODO: need to factor in battery charge metrics */
|
|
}
|
|
|
|
/* check the remaining time is under a set limit, to deal with broken
|
|
primary batteries rate */
|
|
if (time_to_empty > (20 * 60 * 60))
|
|
time_to_empty = 0;
|
|
if (time_to_full > (20 * 60 * 60))
|
|
time_to_full = 0;
|
|
|
|
/* set the old status */
|
|
supply->priv->energy_old = energy;
|
|
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;
|
|
|
|
g_object_set (device,
|
|
"energy", energy,
|
|
"energy-full", energy_full,
|
|
"energy-full-design", energy_full_design,
|
|
"energy-rate", energy_rate,
|
|
"percentage", percentage,
|
|
"state", state,
|
|
"voltage", voltage,
|
|
"time-to-empty", time_to_empty,
|
|
"time-to-full", time_to_full,
|
|
NULL);
|
|
|
|
out:
|
|
g_free (technology_native);
|
|
g_free (manufacturer);
|
|
g_free (model_name);
|
|
g_free (serial_number);
|
|
g_free (status);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_poll_battery:
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_poll_battery (DkpDeviceSupply *supply)
|
|
{
|
|
DkpDevice *device = DKP_DEVICE (supply);
|
|
|
|
egg_debug ("No updates on supply %s for %i seconds; forcing update", dkp_device_get_object_path (device), DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT);
|
|
supply->priv->poll_timer_id = 0;
|
|
dkp_device_supply_refresh (device);
|
|
|
|
/* never repeat */
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_coldplug:
|
|
*
|
|
* Return %TRUE on success, %FALSE if we failed to get data and should be removed
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_coldplug (DkpDevice *device)
|
|
{
|
|
DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
|
|
gboolean ret = FALSE;
|
|
GUdevDevice *native;
|
|
const gchar *native_path;
|
|
gchar *device_type = NULL;
|
|
DkpDeviceType type = DKP_DEVICE_TYPE_UNKNOWN;
|
|
|
|
dkp_device_supply_reset_values (supply);
|
|
|
|
/* detect what kind of device we are */
|
|
native = G_UDEV_DEVICE (dkp_device_get_native (device));
|
|
native_path = g_udev_device_get_sysfs_path (native);
|
|
if (native_path == NULL) {
|
|
egg_warning ("could not get native path for %p", device);
|
|
goto out;
|
|
}
|
|
|
|
/* try to detect using the device type */
|
|
device_type = dkp_device_supply_get_string (native_path, "type");
|
|
if (device_type != NULL) {
|
|
if (g_ascii_strcasecmp (device_type, "mains") == 0) {
|
|
type = DKP_DEVICE_TYPE_LINE_POWER;
|
|
} else if (g_ascii_strcasecmp (device_type, "battery") == 0) {
|
|
type = DKP_DEVICE_TYPE_BATTERY;
|
|
} else {
|
|
egg_warning ("did not recognise type %s, please report", device_type);
|
|
}
|
|
}
|
|
|
|
/* if reading the device type did not work, use the previous method */
|
|
if (type == DKP_DEVICE_TYPE_UNKNOWN) {
|
|
if (sysfs_file_exists (native_path, "online")) {
|
|
type = DKP_DEVICE_TYPE_LINE_POWER;
|
|
} else {
|
|
/* this is a good guess as UPS and CSR are not in the kernel */
|
|
type = DKP_DEVICE_TYPE_BATTERY;
|
|
}
|
|
}
|
|
|
|
/* set the value */
|
|
g_object_set (device, "type", type, NULL);
|
|
|
|
/* coldplug values */
|
|
ret = dkp_device_supply_refresh (device);
|
|
out:
|
|
g_free (device_type);
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_setup_poll:
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_setup_poll (DkpDevice *device)
|
|
{
|
|
DkpDeviceState state;
|
|
DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
|
|
|
|
g_object_get (device, "state", &state, NULL);
|
|
|
|
/* don't setup the poll only if we're sure */
|
|
if (!supply->priv->enable_poll)
|
|
goto out;
|
|
|
|
/* if it's unknown, poll faster than we would normally */
|
|
if (state == DKP_DEVICE_STATE_UNKNOWN &&
|
|
supply->priv->unknown_retries < DKP_DEVICE_SUPPLY_UNKNOWN_RETRIES) {
|
|
supply->priv->poll_timer_id =
|
|
g_timeout_add_seconds (DKP_DEVICE_SUPPLY_UNKNOWN_TIMEOUT,
|
|
(GSourceFunc) dkp_device_supply_poll_battery, supply);
|
|
/* increase count, we don't want to poll at 0.5Hz forever */
|
|
supply->priv->unknown_retries++;
|
|
goto out;
|
|
}
|
|
|
|
/* any other state just fall back */
|
|
supply->priv->poll_timer_id =
|
|
g_timeout_add_seconds (DKP_DEVICE_SUPPLY_REFRESH_TIMEOUT,
|
|
(GSourceFunc) dkp_device_supply_poll_battery, supply);
|
|
out:
|
|
return (supply->priv->poll_timer_id != 0);
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_refresh:
|
|
*
|
|
* Return %TRUE on success, %FALSE if we failed to refresh or no data
|
|
**/
|
|
static gboolean
|
|
dkp_device_supply_refresh (DkpDevice *device)
|
|
{
|
|
gboolean ret;
|
|
GTimeVal timeval;
|
|
DkpDeviceSupply *supply = DKP_DEVICE_SUPPLY (device);
|
|
DkpDeviceType type;
|
|
|
|
if (supply->priv->poll_timer_id > 0) {
|
|
g_source_remove (supply->priv->poll_timer_id);
|
|
supply->priv->poll_timer_id = 0;
|
|
}
|
|
|
|
g_object_get (device, "type", &type, NULL);
|
|
switch (type) {
|
|
case DKP_DEVICE_TYPE_LINE_POWER:
|
|
ret = dkp_device_supply_refresh_line_power (supply);
|
|
break;
|
|
case DKP_DEVICE_TYPE_BATTERY:
|
|
ret = dkp_device_supply_refresh_battery (supply);
|
|
|
|
/* Seems that we don't get change uevents from the
|
|
* kernel on some BIOS types */
|
|
dkp_device_supply_setup_poll (device);
|
|
break;
|
|
default:
|
|
g_assert_not_reached ();
|
|
break;
|
|
}
|
|
|
|
/* reset time if we got new data */
|
|
if (ret) {
|
|
g_get_current_time (&timeval);
|
|
g_object_set (device, "update-time", (guint64) timeval.tv_sec, NULL);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_init:
|
|
**/
|
|
static void
|
|
dkp_device_supply_init (DkpDeviceSupply *supply)
|
|
{
|
|
supply->priv = DKP_DEVICE_SUPPLY_GET_PRIVATE (supply);
|
|
supply->priv->unknown_retries = 0;
|
|
supply->priv->poll_timer_id = 0;
|
|
supply->priv->enable_poll = TRUE;
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_finalize:
|
|
**/
|
|
static void
|
|
dkp_device_supply_finalize (GObject *object)
|
|
{
|
|
DkpDeviceSupply *supply;
|
|
|
|
g_return_if_fail (object != NULL);
|
|
g_return_if_fail (DKP_IS_SUPPLY (object));
|
|
|
|
supply = DKP_DEVICE_SUPPLY (object);
|
|
g_return_if_fail (supply->priv != NULL);
|
|
|
|
if (supply->priv->poll_timer_id > 0)
|
|
g_source_remove (supply->priv->poll_timer_id);
|
|
|
|
G_OBJECT_CLASS (dkp_device_supply_parent_class)->finalize (object);
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_class_init:
|
|
**/
|
|
static void
|
|
dkp_device_supply_class_init (DkpDeviceSupplyClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
DkpDeviceClass *device_class = DKP_DEVICE_CLASS (klass);
|
|
|
|
object_class->finalize = dkp_device_supply_finalize;
|
|
device_class->get_on_battery = dkp_device_supply_get_on_battery;
|
|
device_class->get_low_battery = dkp_device_supply_get_low_battery;
|
|
device_class->get_online = dkp_device_supply_get_online;
|
|
device_class->coldplug = dkp_device_supply_coldplug;
|
|
device_class->refresh = dkp_device_supply_refresh;
|
|
|
|
g_type_class_add_private (klass, sizeof (DkpDeviceSupplyPrivate));
|
|
}
|
|
|
|
/**
|
|
* dkp_device_supply_new:
|
|
**/
|
|
DkpDeviceSupply *
|
|
dkp_device_supply_new (void)
|
|
{
|
|
return g_object_new (DKP_TYPE_SUPPLY, NULL);
|
|
}
|
|
|