]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sleep: use current charge level to decide suspension
authorSonali Srivastava <srivastava.sonali1@gmail.com>
Fri, 15 Jul 2022 02:26:30 +0000 (07:56 +0530)
committerSonali Srivastava <srivastava.sonali1@gmail.com>
Wed, 20 Jul 2022 09:16:43 +0000 (14:46 +0530)
If battery current charge percentage is below 5% hibernate directly.
Else initial suspend interval is set for HibernateDelaySec. On wakeup
estimate battery discharge rate per hour and if battery charge
percentage is not below 5% system is suspended else hibernated.

src/shared/sleep-config.c
src/shared/sleep-config.h
src/sleep/sleep.c

index a56f2ff618271cb3f6a507c6861553cb60792ef0..980cd5f7a05693068abd1d77896cf77f67bf653d 100644 (file)
@@ -102,6 +102,44 @@ int parse_sleep_config(SleepConfig **ret_sleep_config) {
         return 0;
 }
 
+/* If battery percentage capacity is less than equal to 5% return success */
+int battery_is_low(void) {
+        int r;
+
+         /* We have not used battery capacity_level since value is set to full
+         * or Normal in case acpi is not working properly. In case of no battery
+         * 0 will be returned and system will be suspended for 1st cycle then hibernated */
+
+        r = read_battery_capacity_percentage();
+        if (r == -ENOENT)
+               return false;
+        if (r < 0)
+               return r;
+
+        return r <= 5;
+}
+
+/* Battery percentage capacity fetched from capacity file and if in range 0-100 then returned */
+int read_battery_capacity_percentage(void) {
+        _cleanup_free_ char *bat_cap = NULL;
+        int battery_capacity, r;
+
+        r = read_one_line_file("/sys/class/power_supply/BAT0/capacity", &bat_cap);
+        if (r == -ENOENT)
+               return log_debug_errno(r, "/sys/class/power_supply/BAT0/capacity is unavailable. Assuming no battery exists: %m");
+        if (r < 0)
+               return log_debug_errno(r, "Failed to read /sys/class/power_supply/BAT0/capacity: %m");
+
+        r = safe_atoi(bat_cap, &battery_capacity);
+        if (r < 0)
+               return log_debug_errno(r, "Failed to parse battery capacity: %m");
+
+        if (battery_capacity < 0 || battery_capacity > 100)
+               return log_debug_errno(SYNTHETIC_ERRNO(ERANGE), "Invalid battery capacity");
+
+        return battery_capacity;
+}
+
 int can_sleep_state(char **types) {
         _cleanup_free_ char *text = NULL;
         int r;
index c049a55ad6d0ce4af87eeb638907db036c43ebb2..39d952b9549485fcf3f7c6e601908c277c13c6e4 100644 (file)
@@ -55,6 +55,8 @@ int find_hibernate_location(HibernateLocation **ret_hibernate_location);
 int can_sleep(SleepOperation operation);
 int can_sleep_disk(char **types);
 int can_sleep_state(char **types);
+int read_battery_capacity_percentage(void);
+int battery_is_low(void);
 
 const char* sleep_operation_to_string(SleepOperation s) _const_;
 SleepOperation sleep_operation_from_string(const char *s) _pure_;
index 58003efdfefa86d88429695ba0da84eee42f0d2d..8e36933de233dbea78aeac816aab48ea540fa96e 100644 (file)
@@ -263,41 +263,76 @@ static int execute(
 }
 
 static int execute_s2h(const SleepConfig *sleep_config) {
-        _cleanup_close_ int tfd = -1;
-        struct itimerspec ts = {};
         int r;
 
         assert(sleep_config);
 
-        tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC);
-        if (tfd < 0)
-                return log_error_errno(errno, "Error creating timerfd: %m");
-
-        log_debug("Set timerfd wake alarm for %s",
-                  FORMAT_TIMESPAN(sleep_config->hibernate_delay_sec, USEC_PER_SEC));
-
-        timespec_store(&ts.it_value, sleep_config->hibernate_delay_sec);
-
-        r = timerfd_settime(tfd, 0, &ts, NULL);
-        if (r < 0)
-                return log_error_errno(errno, "Error setting hibernate timer: %m");
-
-        r = execute(sleep_config, SLEEP_SUSPEND, NULL);
-        if (r < 0)
-                return r;
-
-        r = fd_wait_for_event(tfd, POLLIN, 0);
-        if (r < 0)
-                return log_error_errno(r, "Error polling timerfd: %m");
-        if (!FLAGS_SET(r, POLLIN)) /* We woke up before the alarm time, we are done. */
-                return 0;
+        while (battery_is_low() == 0) {
+                _cleanup_close_ int tfd = -1;
+                struct itimerspec ts = {};
+                usec_t suspend_interval = sleep_config->hibernate_delay_sec, before_timestamp = 0, after_timestamp = 0;
+                bool woken_by_timer;
+                int last_capacity = 0, current_capacity = 0, estimated_discharge_rate = 0;
+
+                tfd = timerfd_create(CLOCK_BOOTTIME_ALARM, TFD_NONBLOCK|TFD_CLOEXEC);
+                if (tfd < 0)
+                        return log_error_errno(errno, "Error creating timerfd: %m");
+
+                /* Store current battery capacity and current time before suspension */
+                r = read_battery_capacity_percentage();
+                if (r >= 0) {
+                        last_capacity = r;
+                        log_debug("Current battery charge percentage: %d%%", last_capacity);
+                        before_timestamp = now(CLOCK_BOOTTIME);
+                } else if (r == -ENOENT)
+                        /* In case of no battery, system suspend interval will be set to HibernateDelaySec=. */
+                        log_debug_errno(r, "Suspend Interval value set to %s: %m", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC));
+                else
+                        return log_error_errno(r, "Error fetching battery capacity percentage: %m");
+
+                log_debug("Set timerfd wake alarm for %s", FORMAT_TIMESPAN(suspend_interval, USEC_PER_SEC));
+                /* Wake alarm for system with or without battery to hibernate or estimate discharge rate whichever is applicable */
+                timespec_store(&ts.it_value, suspend_interval);
+
+                if (timerfd_settime(tfd, 0, &ts, NULL) < 0)
+                        return log_error_errno(errno, "Error setting battery estimate timer: %m");
+
+                r = execute(sleep_config, SLEEP_SUSPEND, NULL);
+                if (r < 0)
+                        return r;
 
-        tfd = safe_close(tfd);
+                r = fd_wait_for_event(tfd, POLLIN, 0);
+                if (r < 0)
+                        return log_error_errno(r, "Error polling timerfd: %m");
+                /* Store fd_wait status */
+                woken_by_timer = FLAGS_SET(r, POLLIN);
+
+                r = read_battery_capacity_percentage();
+                if (r >= 0) {
+                        current_capacity = r;
+                        log_debug("Current battery charge percentage after wakeup: %d%%", current_capacity);
+                } else if (r == -ENOENT) {
+                        /* In case of no battery, system will be hibernated after 1st cycle of suspend */
+                        log_debug_errno(r, "Battery capacity percentage unavailable, cannot estimate discharge rate: %m");
+                        break;
+                } else
+                        return log_error_errno(r, "Error fetching battery capacity percentage: %m");
+
+                if (current_capacity >= last_capacity)
+                        log_debug("Battery was not discharged during suspension");
+                else {
+                        after_timestamp = now(CLOCK_BOOTTIME);
+                        log_debug("Attempting to estimate battery discharge rate after wakeup from %s sleep", FORMAT_TIMESPAN(after_timestamp - before_timestamp, USEC_PER_HOUR));
+
+                        estimated_discharge_rate = (last_capacity - current_capacity) * USEC_PER_HOUR / (after_timestamp - before_timestamp);
+                }
 
-        /* If woken up after alarm time, hibernate */
-        log_debug("Attempting to hibernate after waking from %s timer",
-                  FORMAT_TIMESPAN(sleep_config->hibernate_delay_sec, USEC_PER_SEC));
+                if (!woken_by_timer)
+                        /* Return as manual wakeup done. This also will return in case battery was charged during suspension */
+                        return 0;
+        }
 
+        log_debug("Attempting to hibernate");
         r = execute(sleep_config, SLEEP_HIBERNATE, NULL);
         if (r < 0) {
                 log_notice("Couldn't hibernate, will try to suspend again.");