monky-2.2.1.0: Monky/Examples/Wifi/Poll.hs
{-
Copyright 2017 Markus Ongyerth
This file is part of Monky.
Monky is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Monky is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with Monky. If not, see <http://www.gnu.org/licenses/>.
-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE NumDecimals #-}
{-|
Module : Monky.Examples.Wifi.Poll
Description : The polling api for wifi information
Maintainer : ongy
Stability : experimental
Portability : Linux
This provides polling access to wifi information.
For the event based version look at "Monky.Examples.Wifi.Event"
-}
module Monky.Examples.Wifi.Poll
( WifiFormat (..)
, WifiPollHandle
, Direction(..)
, getWifiHandle
, getWifiHandle'
, guessWifiHandle
, guessWifiHandle'
)
where
import Data.Int (Int8)
import Data.Maybe (fromMaybe)
import Data.Word (Word8)
import Formatting
import Monky.Examples.Utility
import qualified Monky.Examples.Wifi.Event as E
import Monky.Modules
import Monky.Wifi
import System.Linux.Netlink.GeNetlink.NL80211
import System.Linux.Netlink.GeNetlink.NL80211.StaInfo
import Data.Text (Text)
import qualified Data.Text as T
import Control.Applicative ((<|>), (<$>), (<*>), pure)
-- | The type for polling wifi information
data WifiPollHandle = WH SSIDSocket Interface ((WifiStats, Maybe NL80211Packet) -> Text) Text
-- TODO: Find out which of RX and TX is which from *our* side and document it
-- | Helper type for WifiFormat to specify direction
data Direction
-- | Use TX information
= DirTX
-- | Use TX information
| DirRX
deriving (Show, Eq)
-- | Enum-ish type for converting Wifi information to text
data WifiFormat
-- | The MCSIndex for our connection
= FormatMCS Direction
-- | The minimum MCSIndex for our connection
| FormatMCSMin
-- | The Signal width (in MHz)
| FormatWifiWidth
-- | The Bitrate of our connection
| FormatBitrate Direction
-- | Minimum of TX/RX Bitrate for this station
| FormatBitrateMin
-- | Signal strength from other source
| FormatSignal
-- | Signal strength average
| FormatSignalAverage
| FormatChannel -- ^Print the current networks channel
| FormatName -- ^Print the ESSID of the current network, may look weird because SSIDs are
| FormatFreq -- ^Print the frequency the current network sends on (related to channel)
| FormatText Text -- ^Print a plaintext string
deriving (Show, Eq)
-- |Do the calculation for MBM
-- This is taken from NetworkManager
doMBM :: Int8 -> Word8
doMBM e =
let noiseFloor = -90
signalMax = -20
clamped = min signalMax $ max noiseFloor $ e
in fromIntegral $ 100 - (signalMax - clamped)
getFromDir :: Direction -> StaInfo -> Maybe StaRate
getFromDir DirTX = staTXRate
getFromDir DirRX = staRXRate
pollToEvt :: WifiFormat -> E.WifiFormat
pollToEvt FormatChannel = E.FormatChannel
pollToEvt FormatName = E.FormatName
pollToEvt FormatFreq = E.FormatFreq
pollToEvt (FormatText t) = E.FormatText t
pollToEvt x = error $ "Tried to convert " ++ show x ++ "to Evt? This really shouldn't ever happen"
getExtFun :: WifiFormat -> (WifiStats, StaInfo) -> Text
getExtFun (FormatMCS dir) (_, info) =
case getFromDir dir info of
Nothing -> "No Rate"
Just x -> case rateMCS x <|> rateVHTMCS x of
Nothing -> "No MCS"
Just y -> sformat int y
getExtFun FormatMCSMin (_, info) = fromMaybe "No Rate" $ do
rx <- staRXRate info
tx <- staTXRate info
rmcs <- getMCS rx
tmcs <- getMCS tx
pure . sformat int $ min rmcs tmcs
where getMCS x = rateMCS x <|> rateVHTMCS x
getExtFun FormatWifiWidth (_, info) = -- Width is also in TXRate
case staTXRate info of
Nothing -> "No Rate"
Just x -> case rateWidthFlag x of
Width5MHz -> "5MHz"
Width10MHz -> "10MHz"
Width20MHz -> "20MHz"
Width40MHz -> "40MHz"
Width80MHz -> "80MHz"
Width160MHz -> "160MHz"
getExtFun (FormatBitrate dir) (_, info) = -- Bitrate is in TXRate
case getFromDir dir info of
Nothing -> "No Rate"
Just x -> maybe
"No Bitrate"
(flip convertUnitSI "b" . (* 1e5))
(rateBitrate x)
getExtFun FormatBitrateMin (_, info) = -- Bitrate from RX/TX Rate
let tx = rateBitrate =<< staTXRate info
rx = rateBitrate =<< staRXRate info
in case min <$> rx <*> tx of
Nothing -> "No rates"
Just x -> convertUnitSI (x * 1e5) "b"
getExtFun FormatSignal (_, info) =
case staSignalMBM info of
Nothing -> "No strength"
Just x -> sformat int . doMBM $ fromIntegral x
getExtFun FormatSignalAverage (_, info) =
case staSignalMBMA info of
Nothing -> "No strength"
Just x -> sformat int . doMBM $ fromIntegral x
getExtFun x (stats, _) = E.getTextify (pollToEvt x) stats
getExtFunction :: [WifiFormat] -> (WifiStats, StaInfo) -> Text
getExtFunction xs = T.concat . (sequence . map getExtFun $ xs)
{- |This function is the easiest, but also a bit limited way to get extended wifi information.
With this, all "normal" information will be first, and the extended information will be appended.
-}
getCombiFun :: [WifiFormat] -> ((WifiStats, Maybe NL80211Packet) -> Text)
getCombiFun xs (stat, ext) =
let fun = getExtFunction xs
info = staInfoFromPacket =<< ext
in case info of
Just x -> fun (stat, x)
Nothing -> "Couldn't get wifi station info"
instance PollModule WifiPollHandle where
getOutput (WH s i f d) = do
ret <- getCurrentWifiStats s i
case ret of
Nothing -> pure . pure $ MonkyPlain d
Just x -> do
ext <- getExtendedWifi s i x
pure . pure . MonkyPlain $ f (x, ext)
-- | Lower level version of 'getWifiHandle' for more level of control
getWifiHandle'
:: ((WifiStats, Maybe NL80211Packet) -> Text)
-> Text
-> String
-> IO WifiPollHandle
getWifiHandle' f d n = do
s <- getSSIDSocket
i <- fromMaybe (error ("Could not find interface: " ++ n)) <$> getInterface s n
return (WH s i f d)
-- |Get a wifi handle
getWifiHandle
:: [WifiFormat] -- ^Format "String" for output generation
-> Text -- ^Text that should be displayed when wifi is disconnected
-> String -- ^Name of the interface
-> IO WifiPollHandle
getWifiHandle f d n =
getWifiHandle' (getCombiFun f) d n
-- | Lower level version of 'guessWifiHandle' for more control
guessWifiHandle'
:: ((WifiStats, Maybe NL80211Packet) -> Text)
-> Text -- ^Text that should be displayed when wifi is disconnected
-> IO WifiPollHandle
guessWifiHandle' f d = do
s <- getSSIDSocket
i <- fromMaybe (error "Couldn't find any NL80211 interface") <$> guessInterface s
return (WH s i f d)
{- | Get a wifi handle, guess the interface
Guess isn't quite the right word here. This asks the NL80211 subsystem for a
list of devices and picks the first one.
-}
guessWifiHandle
:: [WifiFormat] -- ^Format "String" for output generation
-> Text -- ^Text that should be displayed when wifi is disconnected
-> IO WifiPollHandle
guessWifiHandle f d =
guessWifiHandle' (getCombiFun f) d