{-# LANGUAGE OverloadedStrings #-} -- | This is a simple library to query the Linux UPower daemon (via -- DBus) for battery information. Currently, it only retrieves -- information for the first battery it finds. module System.Information.Battery ( -- * Types BatteryContext, BatteryInfo(..), BatteryState(..), BatteryTechnology(..), BatteryType(..), -- * Accessors batteryContextNew, getBatteryInfo ) where import Data.Map ( Map ) import qualified Data.Map as M import Data.Maybe ( fromMaybe ) import Data.Word import Data.Int import DBus import DBus.Client import Data.List ( find, isInfixOf ) import Data.Text ( Text ) import qualified Data.Text as T import Safe ( atMay ) -- | An opaque wrapper around some internal library state data BatteryContext = BC Client ObjectPath data BatteryType = BatteryTypeUnknown | BatteryTypeLinePower | BatteryTypeBatteryType | BatteryTypeUps | BatteryTypeMonitor | BatteryTypeMouse | BatteryTypeKeyboard | BatteryTypePda | BatteryTypePhone deriving (Show, Ord, Eq, Enum) data BatteryState = BatteryStateUnknown | BatteryStateCharging | BatteryStateDischarging | BatteryStateEmpty | BatteryStateFullyCharged | BatteryStatePendingCharge | BatteryStatePendingDischarge deriving (Show, Ord, Eq, Enum) data BatteryTechnology = BatteryTechnologyUnknown | BatteryTechnologyLithiumIon | BatteryTechnologyLithiumPolymer | BatteryTechnologyLithiumIronPhosphate | BatteryTechnologyLeadAcid | BatteryTechnologyNickelCadmium | BatteryTechnologyNickelMetalHydride deriving (Show, Ord, Eq, Enum) -- | There are a few fields supported by UPower that aren't exposed -- here.. could be easily. data BatteryInfo = BatteryInfo { batteryNativePath :: Text , batteryVendor :: Text , batteryModel :: Text , batterySerial :: Text -- , batteryUpdateTime :: Time , batteryType :: BatteryType , batteryPowerSupply :: Bool , batteryHasHistory :: Bool , batteryHasStatistics :: Bool , batteryOnline :: Bool , batteryEnergy :: Double , batteryEnergyEmpty :: Double , batteryEnergyFull :: Double , batteryEnergyFullDesign :: Double , batteryEnergyRate :: Double , batteryVoltage :: Double , batteryTimeToEmpty :: Int64 , batteryTimeToFull :: Int64 , batteryPercentage :: Double , batteryIsPresent :: Bool , batteryState :: BatteryState , batteryIsRechargable :: Bool , batteryCapacity :: Double , batteryTechnology :: BatteryTechnology {- , batteryRecallNotice :: Bool , batteryRecallVendor :: Text , batteryRecallUr :: Text -} } -- | Find the first power source that is a battery in the list. The -- simple heuristic is a substring search on 'BAT' firstBattery :: [ObjectPath] -> Maybe ObjectPath firstBattery = find (isInfixOf "BAT" . formatObjectPath) -- | The name of the power daemon bus powerBusName :: BusName powerBusName = "org.freedesktop.UPower" -- | The base object path powerBaseObjectPath :: ObjectPath powerBaseObjectPath = "/org/freedesktop/UPower" -- | A helper to read the variant contents of a dict with a default -- value. readDict :: (IsVariant a) => Map Text Variant -> Text -> a -> a readDict dict key dflt = fromMaybe dflt $ do variant <- M.lookup key dict fromVariant variant -- | Read the variant contents of a dict which is of an unknown integral type. readDictIntegral :: Map Text Variant -> Text -> Int32 -> Int readDictIntegral dict key dflt = fromMaybe (fromIntegral dflt) $ do v <- M.lookup key dict case variantType v of TypeWord8 -> return $ fromIntegral (f v :: Word8) TypeWord16 -> return $ fromIntegral (f v :: Word16) TypeWord32 -> return $ fromIntegral (f v :: Word32) TypeWord64 -> return $ fromIntegral (f v :: Word64) TypeInt16 -> return $ fromIntegral (f v :: Int16) TypeInt32 -> return $ fromIntegral (f v :: Int32) TypeInt64 -> return $ fromIntegral (f v :: Int64) _ -> Nothing where f :: (Num a, IsVariant a) => Variant -> a f = fromMaybe (fromIntegral dflt) . fromVariant -- | Query the UPower daemon about information on a specific battery. -- If some fields are not actually present, they may have bogus values -- here. Don't bet anything critical on it. getBatteryInfo :: BatteryContext -> IO (Maybe BatteryInfo) getBatteryInfo (BC systemConn battPath) = do -- Grab all of the properties of the battery each call with one -- message. reply <- call_ systemConn (methodCall battPath "org.freedesktop.DBus.Properties" "GetAll") { methodCallDestination = Just "org.freedesktop.UPower" , methodCallBody = [toVariant $ T.pack "org.freedesktop.UPower.Device"] } return $ do body <- methodReturnBody reply `atMay` 0 dict <- fromVariant body return BatteryInfo { batteryNativePath = readDict dict "NativePath" "" , batteryVendor = readDict dict "Vendor" "" , batteryModel = readDict dict "Model" "" , batterySerial = readDict dict "Serial" "" , batteryType = toEnum $ fromIntegral $ readDictIntegral dict "Type" 0 , batteryPowerSupply = readDict dict "PowerSupply" False , batteryHasHistory = readDict dict "HasHistory" False , batteryHasStatistics = readDict dict "HasStatistics" False , batteryOnline = readDict dict "Online" False , batteryEnergy = readDict dict "Energy" 0.0 , batteryEnergyEmpty = readDict dict "EnergyEmpty" 0.0 , batteryEnergyFull = readDict dict "EnergyFull" 0.0 , batteryEnergyFullDesign = readDict dict "EnergyFullDesign" 0.0 , batteryEnergyRate = readDict dict "EnergyRate" 0.0 , batteryVoltage = readDict dict "Voltage" 0.0 , batteryTimeToEmpty = readDict dict "TimeToEmpty" 0 , batteryTimeToFull = readDict dict "TimeToFull" 0 , batteryPercentage = readDict dict "Percentage" 0.0 , batteryIsPresent = readDict dict "IsPresent" False , batteryState = toEnum $ readDictIntegral dict "State" 0 , batteryIsRechargable = readDict dict "IsRechargable" True , batteryCapacity = readDict dict "Capacity" 0.0 , batteryTechnology = toEnum $ fromIntegral $ readDictIntegral dict "Technology" 0 } -- | Construct a battery context if possible. This could fail if the -- UPower daemon is not running. The context can be used to get -- actual battery state with 'getBatteryInfo'. batteryContextNew :: IO (Maybe BatteryContext) batteryContextNew = do systemConn <- connectSystem -- First, get the list of devices. For now, we just get the stats -- for the first battery reply <- call_ systemConn (methodCall powerBaseObjectPath "org.freedesktop.UPower" "EnumerateDevices") { methodCallDestination = Just powerBusName } return $ do body <- methodReturnBody reply `atMay` 0 powerDevices <- fromVariant body battPath <- firstBattery powerDevices return $ BC systemConn battPath