diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Xmobar/Plugins/Monitors/Batt/Linux.hs | 182 | 
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) | 
