summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorPatrick Günther <info@paddybu.de>2022-04-18 11:30:25 +0200
committerPatrick Günther <info@paddybu.de>2022-04-18 11:30:25 +0200
commit3147ef88ac428d2e752aa5db30ef52cf580716cd (patch)
tree455f9fcdb42a8cd64d45da25a88921b91e8fef60
parentd1b9edff080b0de64c5445c2ecfa833cd60d18af (diff)
downloadxmobar-3147ef88ac428d2e752aa5db30ef52cf580716cd.tar.gz
xmobar-3147ef88ac428d2e752aa5db30ef52cf580716cd.tar.bz2
Split up battery reading functions; fixed units
Every quantity is now read in its own function and Maybe and pattern matching is used for dealing with absent files. On top of that all units should be fixed now.
-rw-r--r--src/Xmobar/Plugins/Monitors/Batt/Linux.hs182
1 files changed, 113 insertions, 69 deletions
diff --git a/src/Xmobar/Plugins/Monitors/Batt/Linux.hs b/src/Xmobar/Plugins/Monitors/Batt/Linux.hs
index 362c529..9b53f65 100644
--- a/src/Xmobar/Plugins/Monitors/Batt/Linux.hs
+++ b/src/Xmobar/Plugins/Monitors/Batt/Linux.hs
@@ -24,21 +24,44 @@ import Control.Monad (unless)
import Control.Exception (SomeException, handle)
import System.FilePath ((</>))
import System.IO (IOMode(ReadMode), hGetLine, withFile, Handle)
-import System.Posix.Files (fileExist)
import Data.List (sort, sortBy, group)
import Data.Maybe (fromMaybe)
import Data.Ord (comparing)
import Text.Read (readMaybe)
data Files = Files
- { fFull :: String
- , fNow :: String
+ { fEFull :: String
+ , fCFull :: String
+ , fEFullDesign :: String
+ , fCFullDesign :: String
+ , fENow :: String
+ , fCNow :: String
, fVoltage :: String
, fVoltageMin :: String
- , fCurrent :: Maybe String
- , fPower :: Maybe String
+ , fCurrent :: String
+ , fPower :: String
, fStatus :: String
- } | NoFiles deriving Eq
+ } deriving Eq
+
+-- the default basenames of the possibly available attributes exposed by the kernel
+defFileBasenames :: Files
+defFileBasenames = Files {
+ fEFull = "energy_full"
+ , fCFull = "charge_full"
+ , fEFullDesign = "energy_full_design"
+ , fCFullDesign = "charge_full_design"
+ , fENow = "energy_now"
+ , fCNow = "charge_now"
+ , fVoltage = "voltage_now"
+ , fVoltageMin = "voltage_min_design"
+ , fCurrent = "current_now"
+ , fPower = "power_now"
+ , fStatus = "status" }
+
+-- prefix all files in a Files object by a given prefix
+-- I couldn't find a better way to do this
+prefixFiles :: String -> Files -> Files
+prefixFiles p (Files a b c d e f g h i j k) = Files (p </> a) (p </> b) (p </> c) (p </> d) (p </> e) (p </> f) (p </> g) (p </> h) (p </> i) (p </> j) (p </> k)
data Battery = Battery
{ full :: !Float
@@ -50,79 +73,101 @@ data Battery = Battery
sysDir :: FilePath
sysDir = "/sys/class/power_supply"
-safeFileExist :: String -> String -> IO Bool
-safeFileExist d f = handle noErrors $ fileExist (d </> f)
- where noErrors = const (return False) :: SomeException -> IO Bool
-
-batteryFiles :: String -> IO Files
-batteryFiles bat =
- do is_charge <- exists "charge_now"
- is_energy <- if is_charge then return False else exists "energy_now"
- plain <- exists (if is_charge then "charge_full" else "energy_full")
- has_power <- exists powerNowPath
- has_current <- exists currentNowPath
- let pf = if has_power then Just powerNowPath else Nothing
- cf = if has_current then Just currentNowPath else Nothing
- sf = if plain then "" else "_design"
- return $ case (is_charge, is_energy) of
- (True, _) -> files "charge" cf pf sf
- (_, True) -> files "energy" cf pf sf
- _ -> NoFiles
- where prefix = sysDir </> bat
- justPrefix a = Just $ prefix </> a
- exists = safeFileExist prefix
- files ch cf pf sf = Files { fFull = prefix </> ch ++ "_full" ++ sf
- , fNow = prefix </> ch ++ "_now"
- , fCurrent = maybe Nothing justPrefix cf
- , fPower = maybe Nothing justPrefix pf
- , fVoltage = prefix </> "voltage_now"
- , fVoltageMin = prefix </> "voltage_min_design"
- , fStatus = prefix </> "status"}
- currentNowPath = "current_now"
- powerNowPath = "power_now"
+-- get the filenames for a given battery name
+batteryFiles :: String -> Files
+batteryFiles bat = prefixFiles (sysDir </> bat) defFileBasenames
haveAc :: FilePath -> IO Bool
haveAc f =
- handle (onError False) $ withFile (sysDir </> f) ReadMode (fmap (== "1") . hGetLine)
-
-readBatPower :: Float -> Files -> IO Float
-
-readBatPower sc (Files {fPower = Just p}) =
- do valp <- grabNumber p
- return $ valp / sc
-
-readBatPower sc (Files {fPower = Nothing, fCurrent = Just c, fVoltage = v}) =
- do valv <- grabNumber v
- valc <- grabNumber c
- return $ valc * valv / (sc * sc)
-
-readBatPower _ _ = do return 999
-
+ handle (onFileError False) $ withFile (sysDir </> f) ReadMode (fmap (== "1") . hGetLine)
+
+-- retrieve the currently drawn power in Watt
+-- sc is a scaling factor which by kernel documentation must be 1e6
+readBatPower :: Float -> Files -> IO (Maybe Float)
+readBatPower sc f =
+ do pM <- grabNumber $ fPower f
+ cM <- grabNumber $ fCurrent f
+ vM <- grabNumber $ fVoltage f
+ return $ case (pM, cM, vM) of
+ (Just pVal, _, _) -> Just $ pVal / sc
+ (_, Just cVal, Just vVal) -> Just $ cVal * vVal / (sc * sc)
+ (_, _, _) -> Nothing
+
+-- retrieve the maximum capacity in Watt hours
+-- sc is a scaling factor which by kernel documentation must be 1e6
+-- on getting the voltage: using voltage_min_design will probably underestimate
+-- the actual energy content of the battery and using voltage_now will probably
+-- overestimate it.
+readBatCapacityFull :: Float -> Files -> IO (Maybe Float)
+readBatCapacityFull sc f =
+ do cM <- grabNumber $ fCFull f
+ eM <- grabNumber $ fEFull f
+ cdM <- grabNumber $ fCFullDesign f
+ edM <- grabNumber $ fEFullDesign f
+ vM <- grabNumber $ fVoltageMin f -- not sure if Voltage or VoltageMin is more accurate and if both are always available
+ return $ case (eM, cM, edM, cdM, vM) of
+ (Just eVal, _, _, _, _) -> Just $ eVal / sc
+ (_, Just cVal, _, _, Just vVal) -> Just $ cVal * vVal / (sc * sc)
+ (_, _, Just eVal, _, _) -> Just $ eVal / sc
+ (_, _, _, Just cVal, Just vVal) -> Just $ cVal * vVal / (sc * sc)
+ (_, _, _, _, _) -> Nothing
+
+-- retrieve the current capacity in Watt hours
+-- sc is a scaling factor which by kernel documentation must be 1e6
+-- on getting the voltage: using voltage_min_design will probably underestimate
+-- the actual energy content of the battery and using voltage_now will probably
+-- overestimate it.
+readBatCapacityNow :: Float -> Files -> IO (Maybe Float)
+readBatCapacityNow sc f =
+ do cM <- grabNumber $ fCNow f
+ eM <- grabNumber $ fENow f
+ vM <- grabNumber $ fVoltageMin f -- not sure if Voltage or VoltageMin is more accurate and if both are always available
+ return $ case (eM, cM, vM) of
+ (Just eVal, _, _) -> Just $ eVal / sc
+ (_, Just cVal, Just vVal) -> Just $ cVal * vVal / (sc * sc)
+ (_, _, _) -> Nothing
+
+readBatStatus :: Files -> IO (Maybe String)
+readBatStatus f = grabString $ fStatus f
+
+-- "resolve" a Maybe to a given default value if it is Nothing
+setDefault :: a -> IO (Maybe a) -> IO a
+setDefault def ioval =
+ do m <- ioval
+ return $ case m of (Just v) -> v
+ Nothing -> def
+
+-- collect all relevant battery values with defaults of not available
readBattery :: Float -> Files -> IO Battery
-readBattery _ NoFiles = return $ Battery 0 0 0 "Unknown"
readBattery sc files =
- do a <- grabNumber $ fFull files
- b <- grabNumber $ fNow files
- p <- readBatPower sc files
- s <- grabString $ fStatus files
- let a' = max a b -- sometimes the reported max charge is lower than
- return $ Battery (3600 * a' / sc) -- wattseconds
- (3600 * b / sc) -- wattseconds
- (abs p) -- watts
+ do cFull <- setDefault 0 $ readBatCapacityFull sc files
+ cNow <- setDefault 0 $ readBatCapacityNow sc files
+ pwr <- setDefault 0 $ readBatPower sc files
+ s <- setDefault "Unknown" $ readBatStatus files
+ let cFull' = max cFull cNow -- sometimes the reported max charge is lower than
+ return $ Battery (3600 * cFull') -- wattseconds
+ (3600 * cNow) -- wattseconds
+ (abs pwr) -- watts
s -- string: Discharging/Charging/Full
-grabNumber :: (Num a, Read a) => FilePath -> IO a
-grabNumber = grabFile (-1) (fmap read . hGetLine)
+grabNumber :: (Num a, Read a) => FilePath -> IO (Maybe a)
+grabNumber = grabFile (fmap read . hGetLine)
-grabString :: FilePath -> IO String
-grabString = grabFile "Unknown" hGetLine
+grabString :: FilePath -> IO (Maybe String)
+grabString = grabFile hGetLine
-grabFile :: a -> (Handle -> IO a) -> FilePath -> IO a
-grabFile returnOnError readMode f = handle (onFileError returnOnError) $ withFile f ReadMode readMode
+-- grab file contents returning Nothing if the file doesn't exist or any other error occurs
+grabFile :: (Handle -> IO a) -> FilePath -> IO (Maybe a)
+grabFile readMode f = handle (onFileError Nothing) (withFile f ReadMode (doJust . readMode))
onFileError :: a -> SomeException -> IO a
onFileError returnOnError = const (return returnOnError)
+doJust :: IO a -> IO (Maybe a)
+doJust a =
+ do v <- a
+ return $ Just v
+
-- sortOn is only available starting at ghc 7.10
sortOn :: Ord b => (a -> b) -> [a] -> [a]
sortOn f =
@@ -133,12 +178,11 @@ mostCommonDef x xs = head $ last $ [x] : sortOn length (group xs)
readBatteries :: BattOpts -> [String] -> IO Result
readBatteries opts bfs =
- do bfs' <- mapM batteryFiles bfs
- let bfs'' = filter (/= NoFiles) bfs'
+ do let bfs'' = map batteryFiles bfs
bats <- mapM (readBattery (scale opts)) (take 3 bfs'')
ac <- haveAc (onlineFile opts)
let sign = if ac then 1 else -1
- ft = sum (map full bats)
+ ft = sum (map full bats) -- total capacity when full
left = if ft > 0 then sum (map now bats) / ft else 0
watts = sign * sum (map power bats)
time = if watts == 0 then 0 else max 0 (sum $ map time' bats)