(in-package :common-lisp-user) (defpackage :jeho.utilities.linux.battery (:use :common-lisp) (:nicknames :battery) (:export :get-battery-info :charge-status :remaining-time :ac-online-p :number :serial-number :remaining-capacity :present-voltage :present-rate :presentp :model-number :last-full-capacity :design-voltage :design-capacity-warning :design-capacity-low :design-capacity :charging-state :capacity-state :capacity-granularity-2 :capacity-granularity-1 :type :technology :oem-info)) (in-package :jeho.utilities.linux.battery) (defvar *numerical-fields* ;;; Fields to parse as integers '("remaining capacity" "present voltage" "present rate" "last full capacity" "design voltage" "design capacity warning" "design capacity low" "design capacity" "capacity granularity 2" "capacity granularity 1")) (defstruct (battery (:constructor make-battery (number serial-number remaining-capacity present-voltage present-rate presentp model-number last-full-capacity design-voltage design-capacity-warning design-capacity-low design-capacity charging-state capacity-state capacity-granularity-2 capacity-granularity-1 type technology oem-info))) number serial-number remaining-capacity present-voltage present-rate presentp model-number last-full-capacity design-voltage design-capacity-warning design-capacity-low design-capacity charging-state capacity-state capacity-granularity-2 capacity-granularity-1 type technology oem-info) (defun split-field (field) (let ((sep-pos (position #\: field))) (when sep-pos (cons (subseq field 0 sep-pos) (let ((value (subseq field (1+ sep-pos)))) (if (string= value "") nil (string-trim '(#\Space) value))))))) (defun gather-fields (directory file) (with-open-file (stream (make-pathname :directory directory :name file) :direction :input) (loop for l = (read-line stream nil) while l collect (split-field l)))) (defun parse-to-integer-maybe (field) (if (member (car field) *numerical-fields* :test #'string=) (parse-integer (cdr field) :junk-allowed t) (cdr field))) (defun get-battery-info () (loop for dir in (mapcar #'pathname-directory (directory "/proc/acpi/battery/*")) for fields = (nconc (gather-fields dir "state") (gather-fields dir "info")) do (print fields) when (string= (cdr (assoc "present" fields :test #'string=)) "yes") collect (apply #'make-battery (car (last dir)) ; BAT0, BAT1, etc. (mapcar #'parse-to-integer-maybe (sort (remove-duplicates fields :test #'string= :key #'car) #'string> :key #'car))))) (defun charge-status (&optional battery-info) (loop for battery in (or battery-info (get-battery-info)) sum (battery-remaining-capacity battery) into remaining sum (battery-last-full-capacity battery) into capacity finally (return (/ remaining capacity)))) (defun remaining-time (&optional battery-info) (loop for battery in (or battery-info (get-battery-info)) sum (battery-remaining-capacity battery) into remaining ;; only one battery will actually have a present rate maximize (battery-present-rate battery) into rate finally (return (and (> rate 0) (* 60 (/ remaining rate)))))) (defun ac-online-p () (with-open-file (s "/proc/acpi/ac_adapter/AC/state") (if (search "on-line" (read-line s nil)) t nil)))