/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2008 David Zeuthen * Copyright (C) 2008 Richard Hughes * * 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 "config.h" #include #include #include #include #include #include #include #include #include "up-config.h" #include "up-types.h" #include "up-constants.h" #include "up-device-supply.h" #include "up-backend-linux-private.h" enum { PROP_0, PROP_IGNORE_SYSTEM_PERCENTAGE }; #define UP_DEVICE_SUPPLY_CHARGED_THRESHOLD 90.0f /* % */ #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 typedef enum { REFRESH_RESULT_FAILURE = 0, REFRESH_RESULT_SUCCESS = 1, REFRESH_RESULT_NO_DATA } RefreshResult; struct UpDeviceSupplyPrivate { guint poll_timer_id; gboolean has_coldplug_values; gboolean coldplug_units; gdouble *energy_old; GTimeVal *energy_old_timespec; guint energy_old_first; gdouble rate_old; guint unknown_retries; gint64 last_unknown_retry; gboolean disable_battery_poll; /* from configuration */ gboolean is_power_supply; gboolean shown_invalid_voltage_warning; gboolean ignore_system_percentage; }; G_DEFINE_TYPE_WITH_PRIVATE (UpDeviceSupply, up_device_supply, UP_TYPE_DEVICE) static gboolean up_device_supply_refresh (UpDevice *device); static void up_device_supply_setup_unknown_poll (UpDevice *device, UpDeviceState state); static UpDeviceKind up_device_supply_guess_type (GUdevDevice *native, const char *native_path); static RefreshResult up_device_supply_refresh_line_power (UpDeviceSupply *supply) { UpDevice *device = UP_DEVICE (supply); GUdevDevice *native; /* is providing power to computer? */ g_object_set (device, "power-supply", supply->priv->is_power_supply, NULL); /* get new AC value */ native = G_UDEV_DEVICE (up_device_get_native (device)); g_object_set (device, "online", g_udev_device_get_sysfs_attr_as_int_uncached (native, "online"), NULL); return REFRESH_RESULT_SUCCESS; } /** * up_device_supply_reset_values: **/ 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; 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, "vendor", NULL, "model", NULL, "serial", NULL, "update-time", (guint64) 0, "power-supply", FALSE, "online", FALSE, "energy", (gdouble) 0.0, "is-present", FALSE, "is-rechargeable", FALSE, "has-history", FALSE, "has-statistics", FALSE, "state", UP_DEVICE_STATE_UNKNOWN, "capacity", (gdouble) 0.0, "energy-empty", (gdouble) 0.0, "energy-full", (gdouble) 0.0, "energy-full-design", (gdouble) 0.0, "energy-rate", (gdouble) 0.0, "voltage", (gdouble) 0.0, "time-to-empty", (gint64) 0, "time-to-full", (gint64) 0, "percentage", (gdouble) 0.0, "temperature", (gdouble) 0.0, "technology", UP_DEVICE_TECHNOLOGY_UNKNOWN, NULL); } /** * up_device_supply_get_on_battery: **/ static gboolean up_device_supply_get_on_battery (UpDevice *device, gboolean *on_battery) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); UpDeviceKind type; UpDeviceState state; gboolean is_power_supply; gboolean is_present; g_return_val_if_fail (UP_IS_DEVICE_SUPPLY (supply), FALSE); g_return_val_if_fail (on_battery != NULL, FALSE); g_object_get (device, "type", &type, "state", &state, "is-present", &is_present, "power-supply", &is_power_supply, NULL); if (!is_power_supply) return FALSE; if (type != UP_DEVICE_KIND_BATTERY) return FALSE; if (state == UP_DEVICE_STATE_UNKNOWN) return FALSE; if (!is_present) return FALSE; *on_battery = (state == UP_DEVICE_STATE_DISCHARGING); return TRUE; } /** * up_device_supply_get_online: **/ static gboolean up_device_supply_get_online (UpDevice *device, gboolean *online) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); UpDeviceKind type; gboolean online_tmp; g_return_val_if_fail (UP_IS_DEVICE_SUPPLY (supply), FALSE); g_return_val_if_fail (online != NULL, FALSE); g_object_get (device, "type", &type, "online", &online_tmp, NULL); if (type != UP_DEVICE_KIND_LINE_POWER) return FALSE; *online = online_tmp; 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) { gdouble rate = 0.0f; gdouble sum_x = 0.0f; /* sum of the squared times difference */ GTimeVal now; guint i; guint valid_values = 0; /* get the time difference from now and use linear regression to determine * the discharge rate of the battery. */ g_get_current_time (&now); /* 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; /* Compute the discharge per hour, and not per second */ rate /= sum_x / SECONDS_PER_HOUR_F; /* 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; } /** * up_device_supply_convert_device_technology: **/ static UpDeviceTechnology up_device_supply_convert_device_technology (const gchar *type) { if (type == NULL) return UP_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 UP_DEVICE_TECHNOLOGY_LITHIUM_ION; if (g_ascii_strcasecmp (type, "pb") == 0 || g_ascii_strcasecmp (type, "pbac") == 0) return UP_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 UP_DEVICE_TECHNOLOGY_LITHIUM_POLYMER; if (g_ascii_strcasecmp (type, "nimh") == 0) return UP_DEVICE_TECHNOLOGY_NICKEL_METAL_HYDRIDE; if (g_ascii_strcasecmp (type, "life") == 0) return UP_DEVICE_TECHNOLOGY_LITHIUM_IRON_PHOSPHATE; return UP_DEVICE_TECHNOLOGY_UNKNOWN; } /** * up_device_supply_get_string: **/ static gchar * up_device_supply_get_string (GUdevDevice *native, const gchar *key) { gchar *value; /* get value, and strip to remove spaces */ value = g_strdup (g_udev_device_get_sysfs_attr_uncached (native, key)); if (value) g_strstrip (value); /* no value */ if (value == NULL) goto out; /* empty value */ if (value[0] == '\0') { g_free (value); value = NULL; goto out; } out: return value; } /** * up_device_supply_get_design_voltage: **/ static gdouble up_device_supply_get_design_voltage (UpDeviceSupply *device, GUdevDevice *native) { gdouble voltage; gchar *device_type = NULL; /* design maximum */ voltage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "voltage_max_design") / 1000000.0; if (voltage > 1.00f) { g_debug ("using max design voltage"); goto out; } /* design minimum */ voltage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "voltage_min_design") / 1000000.0; if (voltage > 1.00f) { g_debug ("using min design voltage"); goto out; } /* current voltage */ voltage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "voltage_present") / 1000000.0; if (voltage > 1.00f) { g_debug ("using present voltage"); goto out; } /* current voltage, alternate form */ voltage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "voltage_now") / 1000000.0; if (voltage > 1.00f) { g_debug ("using present voltage (alternate)"); goto out; } /* is this a USB device? */ device_type = up_device_supply_get_string (native, "type"); if (device_type != NULL && g_ascii_strcasecmp (device_type, "USB") == 0) { g_debug ("USB device, so assuming 5v"); voltage = 5.0f; goto out; } /* no valid value found; display a warning the first time for each * device */ if (!device->priv->shown_invalid_voltage_warning) { device->priv->shown_invalid_voltage_warning = TRUE; g_warning ("no valid voltage value found for device %s, assuming 10V", g_udev_device_get_sysfs_path (native)); } /* completely guess, to avoid getting zero values */ g_debug ("no voltage values for device %s, using 10V as approximation", g_udev_device_get_sysfs_path (native)); voltage = 10.0f; out: g_free (device_type); return voltage; } /** * up_device_supply_make_safe_string: **/ static void up_device_supply_make_safe_string (gchar *text) { guint i; guint idx = 0; /* no point checking */ if (text == NULL) return; if (g_utf8_validate (text, -1, 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 { g_debug ("invalid char '%c'", text[i]); } } /* ensure null terminated */ text[idx] = '\0'; } static gboolean up_device_supply_units_changed (UpDeviceSupply *supply, GUdevDevice *native) { if (supply->priv->coldplug_units == UP_DEVICE_SUPPLY_COLDPLUG_UNITS_CHARGE) if (g_udev_device_has_sysfs_attr_uncached (native, "charge_now") || g_udev_device_has_sysfs_attr_uncached (native, "charge_avg")) return FALSE; if (supply->priv->coldplug_units == UP_DEVICE_SUPPLY_COLDPLUG_UNITS_ENERGY) if (g_udev_device_has_sysfs_attr_uncached (native, "energy_now") || g_udev_device_has_sysfs_attr_uncached (native, "energy_avg")) return FALSE; return TRUE; } static UpDeviceState up_device_supply_get_state (GUdevDevice *native) { UpDeviceState state; gchar *status; status = up_device_supply_get_string (native, "status"); if (status == NULL || g_ascii_strcasecmp (status, "unknown") == 0 || *status == '\0') { state = UP_DEVICE_STATE_UNKNOWN; } else if (g_ascii_strcasecmp (status, "charging") == 0) state = UP_DEVICE_STATE_CHARGING; else if (g_ascii_strcasecmp (status, "discharging") == 0) state = UP_DEVICE_STATE_DISCHARGING; else if (g_ascii_strcasecmp (status, "full") == 0) state = UP_DEVICE_STATE_FULLY_CHARGED; else if (g_ascii_strcasecmp (status, "empty") == 0) state = UP_DEVICE_STATE_EMPTY; else if (g_ascii_strcasecmp (status, "not charging") == 0) state = UP_DEVICE_STATE_PENDING_CHARGE; else { g_warning ("unknown status string: %s", status); state = UP_DEVICE_STATE_UNKNOWN; } g_free (status); return state; } static gdouble sysfs_get_capacity_level (GUdevDevice *native, UpDeviceLevel *level) { char *str; gdouble ret = -1.0; guint i; struct { const char *str; gdouble percentage; UpDeviceLevel level; } levels[] = { /* In order of most likely to least likely, * Keep in sync with up_daemon_compute_warning_level() */ { "Normal", 55.0, UP_DEVICE_LEVEL_NORMAL }, { "High", 70.0, UP_DEVICE_LEVEL_HIGH }, { "Low", 10.0, UP_DEVICE_LEVEL_LOW }, { "Critical", 5.0, UP_DEVICE_LEVEL_CRITICAL }, { "Full", 100.0, UP_DEVICE_LEVEL_FULL }, { "Unknown", 50.0, UP_DEVICE_LEVEL_UNKNOWN } }; guint len; g_return_val_if_fail (level != NULL, -1.0); if (!g_udev_device_has_sysfs_attr_uncached (native, "capacity_level")) { g_debug ("capacity_level doesn't exist, skipping"); *level = UP_DEVICE_LEVEL_NONE; return -1.0; } *level = UP_DEVICE_LEVEL_UNKNOWN; str = g_strdup (g_udev_device_get_sysfs_attr_uncached (native, "capacity_level")); if (!str) { g_debug ("Failed to read capacity_level!"); return ret; } len = strlen(str); str[len -1] = '\0'; for (i = 0; i < G_N_ELEMENTS(levels); i++) { if (strcmp (levels[i].str, str) == 0) { ret = levels[i].percentage; *level = levels[i].level; break; } } if (ret < 0.0) g_debug ("Could not find a percentage for capacity level '%s'", str); g_free (str); return ret; } static RefreshResult up_device_supply_refresh_battery (UpDeviceSupply *supply, UpDeviceState *out_state) { gchar *technology_native = NULL; gdouble voltage_design; UpDeviceState old_state; UpDeviceState state; UpDevice *device = UP_DEVICE (supply); GUdevDevice *native; gboolean is_present; gdouble energy; gdouble energy_full; gdouble energy_full_design; gdouble energy_rate; gdouble capacity = 100.0f; gdouble percentage = 0.0f; gdouble voltage; gint64 time_to_empty; gint64 time_to_full; gdouble temp; gchar *manufacturer = NULL; gchar *model_name = NULL; gchar *serial_number = NULL; UpDaemon *daemon; gboolean ac_online = FALSE; gboolean has_ac = FALSE; gboolean online; UpDeviceList *devices_list; GPtrArray *devices; guint i; native = G_UDEV_DEVICE (up_device_get_native (device)); /* have we just been removed? */ if (g_udev_device_has_sysfs_attr_uncached (native, "present")) { is_present = g_udev_device_get_sysfs_attr_as_boolean_uncached (native, "present"); } else { /* when no present property exists, handle as present */ is_present = TRUE; } g_object_set (device, "is-present", is_present, NULL); if (!is_present) { up_device_supply_reset_values (supply); g_object_get (device, "state", out_state, NULL); goto out; } /* get the current charge */ energy = g_udev_device_get_sysfs_attr_as_double_uncached (native, "energy_now") / 1000000.0; if (energy < 0.01) energy = g_udev_device_get_sysfs_attr_as_double_uncached (native, "energy_avg") / 1000000.0; /* used to convert A to W later */ voltage_design = up_device_supply_get_design_voltage (supply, native); /* initial values */ if (!supply->priv->has_coldplug_values || up_device_supply_units_changed (supply, native)) { g_object_set (device, "power-supply", supply->priv->is_power_supply, NULL); /* the ACPI spec is bad at defining battery type constants */ technology_native = up_device_supply_get_string (native, "technology"); g_object_set (device, "technology", up_device_supply_convert_device_technology (technology_native), NULL); /* get values which may be blank */ manufacturer = up_device_supply_get_string (native, "manufacturer"); model_name = up_device_supply_get_string (native, "model_name"); serial_number = up_device_supply_get_string (native, "serial_number"); /* some vendors fill this with binary garbage */ up_device_supply_make_safe_string (manufacturer); up_device_supply_make_safe_string (model_name); up_device_supply_make_safe_string (serial_number); 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, NULL); /* these don't change at runtime */ energy_full = g_udev_device_get_sysfs_attr_as_double_uncached (native, "energy_full") / 1000000.0; energy_full_design = g_udev_device_get_sysfs_attr_as_double_uncached (native, "energy_full_design") / 1000000.0; /* convert charge to energy */ if (energy_full < 0.01) { energy_full = g_udev_device_get_sysfs_attr_as_double_uncached (native, "charge_full") / 1000000.0; energy_full_design = g_udev_device_get_sysfs_attr_as_double_uncached (native, "charge_full_design") / 1000000.0; energy_full *= voltage_design; energy_full_design *= voltage_design; supply->priv->coldplug_units = UP_DEVICE_SUPPLY_COLDPLUG_UNITS_CHARGE; } /* the last full should not be bigger than the design */ if (energy_full > energy_full_design) g_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 && energy_full_design > 0.01) { g_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); } state = up_device_supply_get_state (native); /* this is the new value in uW */ if (g_udev_device_has_sysfs_attr (native, "power_now")) { energy_rate = fabs (g_udev_device_get_sysfs_attr_as_double_uncached (native, "power_now") / 1000000.0); } else { gdouble charge_full; /* convert charge to energy */ if (energy < 0.01) { energy = g_udev_device_get_sysfs_attr_as_double_uncached (native, "charge_now") / 1000000.0; if (energy < 0.01) energy = g_udev_device_get_sysfs_attr_as_double_uncached (native, "charge_avg") / 1000000.0; energy *= voltage_design; } charge_full = g_udev_device_get_sysfs_attr_as_double_uncached (native, "charge_full") / 1000000.0; if (charge_full < 0.01) charge_full = g_udev_device_get_sysfs_attr_as_double_uncached (native, "charge_full_design") / 1000000.0; /* If charge_full exists, then current_now is always reported in uA. * In the legacy case, where energy only units exist, and power_now isn't present * current_now is power in uW. */ energy_rate = fabs (g_udev_device_get_sysfs_attr_as_double_uncached (native, "current_now") / 1000000.0); if (charge_full != 0) energy_rate *= voltage_design; } /* some batteries don't update last_full attribute */ if (energy > energy_full) { g_warning ("energy %f bigger than full %f", energy, energy_full); energy_full = energy; } /* present voltage */ voltage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "voltage_now") / 1000000.0; if (voltage < 0.01) voltage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "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; /* Ensure less than 300W, above the 240W possible with USB Power Delivery */ if (energy_rate > 300) energy_rate = 0; /* the hardware reporting failed -- try to calculate this */ if (energy_rate < 0.01) energy_rate = up_device_supply_calculate_rate (supply, energy); /* get a precise percentage */ if (!supply->priv->ignore_system_percentage && g_udev_device_has_sysfs_attr_uncached (native, "capacity")) { percentage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "capacity"); percentage = CLAMP(percentage, 0.0f, 100.0f); /* for devices which provide capacity, but not {energy,charge}_now */ if (energy < 0.1f && energy_full > 0.0f) energy = energy_full * percentage / 100; } else if (energy_full > 0.0f) { percentage = 100.0 * energy / energy_full; percentage = CLAMP(percentage, 0.0f, 100.0f); } /* Some devices report "Not charging" when the battery is full and AC * power is connected. In this situation we should report fully-charged * instead of pending-charge. */ if (state == UP_DEVICE_STATE_PENDING_CHARGE && percentage == 100.0) state = UP_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 == UP_DEVICE_STATE_UNKNOWN && supply->priv->is_power_supply) { daemon = up_device_get_daemon (device); /* If we have any online AC, assume charging, otherwise * discharging */ devices_list = up_daemon_get_device_list (daemon); devices = up_device_list_get_array (devices_list); for (i=0; i < devices->len; i++) { if (up_device_get_online ((UpDevice *) g_ptr_array_index (devices, i), &online)) { has_ac = TRUE; if (online) { ac_online = TRUE; } break; } } g_ptr_array_unref (devices); g_object_unref (devices_list); if (has_ac) { if (ac_online) { if (percentage > UP_DEVICE_SUPPLY_CHARGED_THRESHOLD) state = UP_DEVICE_STATE_FULLY_CHARGED; else state = UP_DEVICE_STATE_CHARGING; } else { if (percentage < 1.0f) state = UP_DEVICE_STATE_EMPTY; else state = UP_DEVICE_STATE_DISCHARGING; } } else { /* only guess when we have only one battery */ if (up_daemon_get_number_devices_of_type (daemon, UP_DEVICE_KIND_BATTERY) == 1) { if (percentage < 1.0f) state = UP_DEVICE_STATE_EMPTY; else state = UP_DEVICE_STATE_DISCHARGING; } /* if we have multiple batteries and don't know their * state, give up and leave it as "unknown". */ } /* print what we did */ g_debug ("guessing battery state '%s': AC present: %i, AC online: %i", up_device_state_to_string (state), has_ac, ac_online); g_object_unref (daemon); } /* if empty, and BIOS does not know what to do */ if (state == UP_DEVICE_STATE_UNKNOWN && energy < 0.01) { g_warning ("Setting %s state empty as unknown and very low", g_udev_device_get_sysfs_path (native)); state = UP_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 == UP_DEVICE_STATE_DISCHARGING) time_to_empty = 3600 * (energy / energy_rate); else if (state == UP_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 > (240 * 60 * 60)) /* ten days for discharging */ time_to_empty = 0; if (time_to_full > (20 * 60 * 60)) /* 20 hours for charging */ time_to_full = 0; /* get temperature */ temp = g_udev_device_get_sysfs_attr_as_double_uncached (native, "temp") / 10.0; /* 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; /* we changed state */ g_object_get (device, "state", &old_state, NULL); 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; } *out_state = state; 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, "temperature", temp, NULL); /* Setup unknown poll again if needed */ up_device_supply_setup_unknown_poll (device, state); out: g_free (technology_native); g_free (manufacturer); g_free (model_name); g_free (serial_number); return REFRESH_RESULT_SUCCESS; } static GUdevDevice * up_device_supply_get_sibling_with_subsystem (GUdevDevice *device, const char *subsystem) { GUdevDevice *parent; GUdevClient *client; GUdevDevice *sibling; const char * class[] = { NULL, NULL }; const char *parent_path; GList *devices, *l; g_return_val_if_fail (device != NULL, NULL); g_return_val_if_fail (subsystem != NULL, NULL); parent = g_udev_device_get_parent (device); if (!parent) return NULL; parent_path = g_udev_device_get_sysfs_path (parent); sibling = NULL; class[0] = subsystem; client = g_udev_client_new (class); devices = g_udev_client_query_by_subsystem (client, subsystem); for (l = devices; l != NULL; l = l->next) { GUdevDevice *d = l->data; GUdevDevice *p; const char *p_path; p = g_udev_device_get_parent (d); if (!p) continue; p_path = g_udev_device_get_sysfs_path (p); if (g_strcmp0 (p_path, parent_path) == 0) { if (sibling != NULL && g_udev_device_get_property_as_boolean (d, "ID_INPUT_KEYBOARD")) { g_clear_object (&sibling); } if (sibling == NULL) sibling = g_object_ref (d); } g_object_unref (p); } g_list_free_full (devices, (GDestroyNotify) g_object_unref); g_object_unref (client); g_object_unref (parent); return sibling; } static RefreshResult up_device_supply_refresh_device (UpDeviceSupply *supply, UpDeviceState *out_state) { UpDeviceState state; UpDevice *device = UP_DEVICE (supply); const gchar *native_path; GUdevDevice *native; gdouble percentage = 0.0f; UpDeviceLevel level = UP_DEVICE_LEVEL_NONE; UpDeviceKind type; native = G_UDEV_DEVICE (up_device_get_native (device)); native_path = g_udev_device_get_sysfs_path (native); /* Try getting a more precise type again */ g_object_get (device, "type", &type, NULL); if (type == UP_DEVICE_KIND_BATTERY) { type = up_device_supply_guess_type (native, native_path); if (type != UP_DEVICE_KIND_BATTERY) g_object_set (device, "type", type, NULL); } /* initial values */ if (!supply->priv->has_coldplug_values) { gchar *model_name; gchar *serial_number; /* get values which may be blank */ model_name = up_device_supply_get_string (native, "model_name"); serial_number = up_device_supply_get_string (native, "serial_number"); if (model_name == NULL && serial_number == NULL) { GUdevDevice *sibling; sibling = up_device_supply_get_sibling_with_subsystem (native, "input"); if (sibling != NULL) { model_name = up_device_supply_get_string (sibling, "name"); serial_number = up_device_supply_get_string (sibling, "uniq"); g_object_unref (sibling); } } /* some vendors fill this with binary garbage */ up_device_supply_make_safe_string (model_name); up_device_supply_make_safe_string (serial_number); g_object_set (device, "is-present", TRUE, "model", model_name, "serial", serial_number, "is-rechargeable", TRUE, "has-history", TRUE, "has-statistics", TRUE, "power-supply", supply->priv->is_power_supply, /* always FALSE */ NULL); /* we only coldplug once, as these values will never change */ supply->priv->has_coldplug_values = TRUE; g_free (model_name); } /* get a precise percentage */ percentage = g_udev_device_get_sysfs_attr_as_double_uncached (native, "capacity"); if (percentage == 0.0f) percentage = sysfs_get_capacity_level (native, &level); if (percentage < 0.0) { /* Probably talking to the device over Bluetooth */ state = UP_DEVICE_STATE_UNKNOWN; g_object_set (device, "state", state, NULL); *out_state = state; return REFRESH_RESULT_NO_DATA; } state = up_device_supply_get_state (native); /* Override whatever the device might have told us * because a number of them are always discharging */ if (percentage == 100.0) state = UP_DEVICE_STATE_FULLY_CHARGED; /* reset unknown counter */ if (state != UP_DEVICE_STATE_UNKNOWN) { g_debug ("resetting unknown timeout after %i retries", supply->priv->unknown_retries); supply->priv->unknown_retries = 0; } g_object_set (device, "percentage", percentage, "battery-level", level, "state", state, NULL); *out_state = state; return REFRESH_RESULT_SUCCESS; } static gboolean up_device_supply_poll_unknown_battery (UpDevice *device) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); g_debug ("Unknown state on supply %s; forcing update after %i seconds", up_device_get_object_path (device), UP_DAEMON_UNKNOWN_TIMEOUT); supply->priv->poll_timer_id = 0; up_device_supply_refresh (device); return FALSE; } static UpDeviceKind up_device_supply_guess_type (GUdevDevice *native, const char *native_path) { gchar *device_type; UpDeviceKind type = UP_DEVICE_KIND_UNKNOWN; device_type = up_device_supply_get_string (native, "type"); if (device_type == NULL) return type; if (g_ascii_strcasecmp (device_type, "mains") == 0) { type = UP_DEVICE_KIND_LINE_POWER; goto out; } if (g_ascii_strcasecmp (device_type, "battery") == 0) { GUdevDevice *sibling; sibling = up_device_supply_get_sibling_with_subsystem (native, "input"); if (sibling) { if (g_udev_device_get_property_as_boolean (sibling, "ID_INPUT_TOUCHPAD")) { type = UP_DEVICE_KIND_TOUCHPAD; } else if (g_udev_device_get_property_as_boolean (sibling, "ID_INPUT_MOUSE")) { type = UP_DEVICE_KIND_MOUSE; } else if (g_udev_device_get_property_as_boolean (sibling, "ID_INPUT_JOYSTICK")) { type = UP_DEVICE_KIND_GAMING_INPUT; } else { type = UP_DEVICE_KIND_KEYBOARD; } g_object_unref (sibling); } if (type == UP_DEVICE_KIND_UNKNOWN) type = UP_DEVICE_KIND_BATTERY; } else if (g_ascii_strcasecmp (device_type, "USB") == 0) { /* use a heuristic to find the device type */ if (g_strstr_len (native_path, -1, "wacom_") != NULL) { type = UP_DEVICE_KIND_TABLET; } else { g_warning ("did not recognise USB path %s, please report", native_path); } } else { g_warning ("did not recognise type %s, please report", device_type); } out: g_free (device_type); return type; } /** * up_device_supply_coldplug: * * Return %TRUE on success, %FALSE if we failed to get data and should be removed **/ static gboolean up_device_supply_coldplug (UpDevice *device) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); GUdevDevice *native; const gchar *native_path; const gchar *scope; UpDeviceKind type; RefreshResult ret; up_device_supply_reset_values (supply); /* detect what kind of device we are */ native = G_UDEV_DEVICE (up_device_get_native (device)); native_path = g_udev_device_get_sysfs_path (native); if (native_path == NULL) { g_warning ("could not get native path for %p", device); return FALSE; } /* try to work out if the device is powering the system */ scope = g_udev_device_get_sysfs_attr (native, "scope"); if (scope != NULL && g_ascii_strcasecmp (scope, "device") == 0) { supply->priv->is_power_supply = FALSE; } else if (scope != NULL && g_ascii_strcasecmp (scope, "system") == 0) { supply->priv->is_power_supply = TRUE; } else { g_debug ("taking a guess for power supply scope"); supply->priv->is_power_supply = TRUE; } /* we don't use separate ACs for devices */ if (supply->priv->is_power_supply == FALSE && !g_udev_device_has_sysfs_attr_uncached (native, "capacity") && !g_udev_device_has_sysfs_attr_uncached (native, "capacity_level")) { g_debug ("Ignoring device AC, we'll monitor the device battery"); return FALSE; } /* try to detect using the device type */ type = up_device_supply_guess_type (native, native_path); /* if reading the device type did not work, use the previous method */ if (type == UP_DEVICE_KIND_UNKNOWN) { if (g_udev_device_has_sysfs_attr_uncached (native, "online")) { type = UP_DEVICE_KIND_LINE_POWER; } else { /* this is a good guess as UPS and CSR are not in the kernel */ type = UP_DEVICE_KIND_BATTERY; } } /* set the value */ g_object_set (device, "type", type, NULL); if (type != UP_DEVICE_KIND_LINE_POWER && type != UP_DEVICE_KIND_BATTERY) up_daemon_start_poll (G_OBJECT (device), (GSourceFunc) up_device_supply_refresh); else if (type == UP_DEVICE_KIND_BATTERY && (!supply->priv->disable_battery_poll || !supply->priv->is_power_supply)) up_daemon_start_poll (G_OBJECT (device), (GSourceFunc) up_device_supply_refresh); /* coldplug values */ ret = up_device_supply_refresh (device); return (ret != REFRESH_RESULT_FAILURE); } /** * up_device_supply_setup_unknown_poll: **/ static void up_device_supply_setup_unknown_poll (UpDevice *device, UpDeviceState state) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); if (supply->priv->disable_battery_poll) return; /* if it's unknown, poll faster than we would normally */ if (supply->priv->unknown_retries < UP_DAEMON_UNKNOWN_RETRIES && (state == UP_DEVICE_STATE_UNKNOWN || up_backend_needs_poll_after_uevent ())) { gint64 now; supply->priv->poll_timer_id = g_timeout_add_seconds (UP_DAEMON_UNKNOWN_TIMEOUT, (GSourceFunc) up_device_supply_poll_unknown_battery, supply); g_source_set_name_by_id (supply->priv->poll_timer_id, "[upower] up_device_supply_poll_unknown_battery (linux)"); /* increase count, we don't want to poll at 0.5Hz forever */ now = g_get_monotonic_time (); if (now - supply->priv->last_unknown_retry > G_USEC_PER_SEC) supply->priv->unknown_retries++; supply->priv->last_unknown_retry = now; } else { /* reset unknown counter */ supply->priv->unknown_retries = 0; } } static void up_device_supply_disable_unknown_poll (UpDevice *device) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); if (supply->priv->poll_timer_id > 0) { g_source_remove (supply->priv->poll_timer_id); supply->priv->poll_timer_id = 0; } } static gboolean up_device_supply_refresh (UpDevice *device) { RefreshResult ret; UpDeviceSupply *supply = UP_DEVICE_SUPPLY (device); UpDeviceKind type; UpDeviceState state; g_object_get (device, "type", &type, NULL); if (type == UP_DEVICE_KIND_LINE_POWER) { ret = up_device_supply_refresh_line_power (supply); } else if (type == UP_DEVICE_KIND_BATTERY && supply->priv->is_power_supply) { up_device_supply_disable_unknown_poll (device); ret = up_device_supply_refresh_battery (supply, &state); } else { ret = up_device_supply_refresh_device (supply, &state); } /* reset time if we got new data */ if (ret == REFRESH_RESULT_SUCCESS) g_object_set (device, "update-time", (guint64) g_get_real_time () / G_USEC_PER_SEC, NULL); return (ret != REFRESH_RESULT_FAILURE); } /** * up_device_supply_init: **/ static void up_device_supply_init (UpDeviceSupply *supply) { UpConfig *config; supply->priv = up_device_supply_get_instance_private (supply); /* 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->shown_invalid_voltage_warning = FALSE; config = up_config_new (); /* Seems that we don't get change uevents from the * kernel on some BIOS types, but if polling * is disabled in the configuration, do nothing */ supply->priv->disable_battery_poll = up_config_get_boolean (config, "NoPollBatteries"); g_object_unref (config); } /** * up_device_supply_finalize: **/ static void up_device_supply_finalize (GObject *object) { UpDeviceSupply *supply; g_return_if_fail (object != NULL); g_return_if_fail (UP_IS_DEVICE_SUPPLY (object)); supply = UP_DEVICE_SUPPLY (object); g_return_if_fail (supply->priv != NULL); up_daemon_stop_poll (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); } static void up_device_supply_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (object); switch (property_id) { case PROP_IGNORE_SYSTEM_PERCENTAGE: supply->priv->ignore_system_percentage = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } } static void up_device_supply_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { UpDeviceSupply *supply = UP_DEVICE_SUPPLY (object); switch (property_id) { case PROP_IGNORE_SYSTEM_PERCENTAGE: g_value_set_flags (value, supply->priv->ignore_system_percentage); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); } } /** * up_device_supply_class_init: **/ static void up_device_supply_class_init (UpDeviceSupplyClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); UpDeviceClass *device_class = UP_DEVICE_CLASS (klass); object_class->finalize = up_device_supply_finalize; object_class->set_property = up_device_supply_set_property; object_class->get_property = up_device_supply_get_property; device_class->get_on_battery = up_device_supply_get_on_battery; device_class->get_online = up_device_supply_get_online; device_class->coldplug = up_device_supply_coldplug; device_class->refresh = up_device_supply_refresh; g_object_class_install_property (object_class, PROP_IGNORE_SYSTEM_PERCENTAGE, g_param_spec_boolean ("ignore-system-percentage", "Ignore system percentage", "Ignore system provided battery percentage", FALSE, G_PARAM_READWRITE)); } /** * up_device_supply_new: **/ UpDeviceSupply * up_device_supply_new (void) { return g_object_new (UP_TYPE_DEVICE_SUPPLY, NULL); }