diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
 # Revision history for evdev
 
+## 2.2.0 -- 05-11-2022
+* More bindings:
+    * `deviceAbsAxis`
+    * `REL_WHEEL_HI_RES` and `REL_HWHEEL_HI_RES`
+    * `setDeviceLED`
+* Open devices with write permissions by default. This is primarily in order to make `setDeviceLED` easier to use.
+* Export event types from `Uinput` module. This means that that module can actually reasonably be used without also importing `Evdev` and selectively hiding things.
+* Wrap results of `deviceUniq` and `devicePhys` in `Maybe`, to avoid possibly dereferencing null pointers.
+
 ## 2.1.0 -- 2021-02-12
 * Add `Evdev.Uinput` module, for creating virtual devices.
 * More functions for querying device properties.
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@
 
 # Permissions
 
-Your user will need to be a member of the `input` group in order to read from devices. Try `usermod -a -G input [username]`.
+Your user will need to be a member of the `input` group in order to read from devices. Try `usermod -a -G input $USER`.
 
 To create virtual devices (i.e. to use the `Evdev.UInput` module) you will need permission to write to `/dev/uinput`. This can usually be achieved by creating a group specially for uinput permissions:
 ```
diff --git a/evdev.cabal b/evdev.cabal
--- a/evdev.cabal
+++ b/evdev.cabal
@@ -1,6 +1,6 @@
 cabal-version:       3.0
 name:                evdev
-version:             2.1.0
+version:             2.2.0
 author:              George Thomas
 maintainer:          George Thomas
 description:         Provides access to the Linux event device interface.
@@ -25,8 +25,8 @@
         extra ^>= {1.6.18, 1.7},
         filepath-bytestring ^>= 1.4.2,
         monad-loops ^>= 0.4.3,
-        rawfilepath ^>= 0.2.4,
-        time ^>= {1.9.3, 1.10, 1.11},
+        rawfilepath ^>= 1.0.0,
+        time ^>= {1.9.3, 1.10, 1.11, 1.12, 1.13},
         unix ^>= 2.7.2,
     default-language: Haskell2010
     default-extensions:
diff --git a/src/Evdev.hs b/src/Evdev.hs
--- a/src/Evdev.hs
+++ b/src/Evdev.hs
@@ -20,6 +20,8 @@
     deviceVendor,
     deviceBustype,
     deviceVersion,
+    deviceAbsAxis,
+    LL.AbsInfo (..),
     -- ** Grabbing
     grabDevice,
     ungrabDevice,
@@ -34,6 +36,8 @@
     -- * Lower-level
     newDeviceFromFd,
     nextEventMay,
+    LL.LEDValue(..),
+    setDeviceLED,
     -- ** C-style types
     -- | These correspond more directly to C's /input_event/ and /timeval/.
     -- They are used internally, but may be useful for advanced users.
@@ -66,7 +70,7 @@
 import System.Posix.Process (getProcessID)
 import System.Posix.Files (readSymbolicLink)
 import System.Posix.ByteString (Fd, RawFilePath)
-import System.Posix.IO.ByteString (OpenMode (ReadOnly), defaultFileFlags, openFd)
+import System.Posix.IO.ByteString (OpenMode (..), defaultFileFlags, openFd)
 
 import qualified Evdev.LowLevel as LL
 import Evdev.Codes
@@ -197,11 +201,14 @@
 toCTimeVal t = LL.CTimeVal n (round $ f * 1_000_000)
     where (n,f) = properFraction t
 
--- | Create a device from a valid path - usually /\/dev\/input\/eventX/ for some /X/.
+{- | Create a device from a valid path - usually /\/dev\/input\/eventX/ for some numeric /X/.
+Use 'newDeviceFromFd' if you need more control over how the device is created.
+-}
 newDevice :: RawFilePath -> IO Device
-newDevice path = newDeviceFromFd =<< openFd path ReadOnly Nothing defaultFileFlags
+newDevice path = newDeviceFromFd =<< openFd path ReadWrite Nothing defaultFileFlags
 
-{- | Generalisation of 'newDevice'.
+{- | Generalisation of 'newDevice', in case one needs control over the file descriptor,
+e.g. in order to set a particular 'System.Posix.FileMode', 'System.Posix.OpenMode', or 'System.Posix.OpenFileFlags'.
 Note that:
 
 > newDevice path = newDeviceFromFd =<< openFd path ReadOnly Nothing defaultFileFlags
@@ -224,9 +231,9 @@
 
 deviceFd :: Device -> IO Fd
 deviceFd = LL.deviceFd . cDevice
-devicePhys :: Device -> IO ByteString
+devicePhys :: Device -> IO (Maybe ByteString)
 devicePhys = join . LL.devicePhys . cDevice
-deviceUniq :: Device -> IO ByteString
+deviceUniq :: Device -> IO (Maybe ByteString)
 deviceUniq = join . LL.deviceUniq . cDevice
 deviceProduct :: Device -> IO Int
 deviceProduct = LL.deviceProduct . cDevice
@@ -248,6 +255,12 @@
 deviceHasEvent dev e = LL.hasEventCode (cDevice dev) typ code
   where (typ,code,_val) = toCEventData e
 
+deviceAbsAxis :: Device -> AbsoluteAxis -> IO (Maybe LL.AbsInfo)
+deviceAbsAxis dev = LL.getAbsInfo (cDevice dev) . fromEnum'
+
+-- | Set the state of a LED on a device.
+setDeviceLED :: Device -> LEDEvent -> LL.LEDValue -> IO ()
+setDeviceLED dev led val = cErrCall "setDeviceLED" dev (LL.libevdev_kernel_set_led_value (cDevice dev) led val)
 
 {- Util -}
 
diff --git a/src/Evdev/Codes.chs b/src/Evdev/Codes.chs
--- a/src/Evdev/Codes.chs
+++ b/src/Evdev/Codes.chs
@@ -582,6 +582,7 @@
     deriving (Bounded, Eq, Ord, Read, Show) #}
 
 -- | Relative changes
+#if defined(REL_WHEEL_HI_RES)
 {#enum define RelativeAxis {
     REL_X as RelX,
     REL_Y as RelY,
@@ -592,8 +593,26 @@
     REL_HWHEEL as RelHwheel,
     REL_DIAL as RelDial,
     REL_WHEEL as RelWheel,
-    REL_MISC as RelMisc}
+    REL_MISC as RelMisc,
+    REL_RESERVED as RelReserved,
+    REL_WHEEL_HI_RES as RelWheelHiRes,
+    REL_HWHEEL_HI_RES as RelHWheelHiRes}
     deriving (Bounded, Eq, Ord, Read, Show) #}
+# else
+{#enum define RelativeAxis {
+    REL_X as RelX,
+    REL_Y as RelY,
+    REL_Z as RelZ,
+    REL_RX as RelRx,
+    REL_RY as RelRy,
+    REL_RZ as RelRz,
+    REL_HWHEEL as RelHwheel,
+    REL_DIAL as RelDial,
+    REL_WHEEL as RelWheel,
+    REL_MISC as RelMisc,
+    REL_RESERVED as RelReserved}
+    deriving (Bounded, Eq, Ord, Read, Show) #}
+#endif
 
 -- | Absolute changes
 {#enum define AbsoluteAxis {
diff --git a/src/Evdev/LowLevel.chs b/src/Evdev/LowLevel.chs
--- a/src/Evdev/LowLevel.chs
+++ b/src/Evdev/LowLevel.chs
@@ -4,7 +4,7 @@
 import Data.ByteString (ByteString,packCString,useAsCString)
 import Data.Coerce (coerce)
 import Data.Int (Int32,Int64)
-import Data.Word (Word16)
+import Data.Word (Word16, Word32)
 import Foreign (Ptr,allocaBytes,mallocBytes,mallocForeignPtrBytes,newForeignPtr_,nullPtr,peek,withForeignPtr)
 import Foreign.C (CInt(..),CLong(..),CUInt(..),CUShort(..),CString)
 import Foreign.C.Error (Errno(Errno), eOK, eAGAIN)
@@ -128,6 +128,7 @@
     , absFlat :: Int32
     , absResolution :: Int32
     }
+    deriving (Show)
 withAbsInfo :: AbsInfo -> (Ptr () -> IO a) -> IO a
 withAbsInfo AbsInfo{..} f = do
     p <- mallocBytes {#sizeof input_absinfo#}
@@ -140,7 +141,21 @@
     pf <- newForeignPtr_ p
     withForeignPtr pf f
 
+--TODO can c2hs make this simpler at all?
+foreign import ccall safe "Evdev/LowLevel.chs.h libevdev_get_abs_info"
+  libevdev_get_abs_info :: Ptr Device -> CUInt -> IO (Ptr ())
+getAbsInfo :: Device -> Word32 -> IO (Maybe AbsInfo)
+getAbsInfo dev x = withDevice dev \devPtr ->
+    libevdev_get_abs_info devPtr (CUInt x) >>= handleNull (pure Nothing) \absinfoPtr -> do
+        CInt absValue <- {#get input_absinfo.value#} absinfoPtr
+        CInt absMinimum <- {#get input_absinfo.minimum#} absinfoPtr
+        CInt absMaximum <- {#get input_absinfo.maximum#} absinfoPtr
+        CInt absFuzz <- {#get input_absinfo.fuzz#} absinfoPtr
+        CInt absFlat <- {#get input_absinfo.flat#} absinfoPtr
+        CInt absResolution <- {#get input_absinfo.resolution#} absinfoPtr
+        pure $ Just AbsInfo{..}
 
+
 {- Simpler functions -}
 
 {#fun libevdev_has_property as hasProperty { `Device', convertEnum `DeviceProperty' } -> `Bool' #}
@@ -148,8 +163,8 @@
 {#fun libevdev_has_event_code as hasEventCode { `Device', `Word16', `Word16' } -> `Bool' #}
 {#fun libevdev_get_fd as deviceFd { `Device' } -> `Fd' Fd #}
 {#fun libevdev_get_name as deviceName { `Device' } -> `IO ByteString' packCString #}
-{#fun libevdev_get_phys as devicePhys { `Device' } -> `IO ByteString' packCString #}
-{#fun libevdev_get_uniq as deviceUniq { `Device' } -> `IO ByteString' packCString #}
+{#fun libevdev_get_phys as devicePhys { `Device' } -> `IO (Maybe ByteString)' packCString' #}
+{#fun libevdev_get_uniq as deviceUniq { `Device' } -> `IO (Maybe ByteString)' packCString' #}
 {#fun libevdev_get_id_product as deviceProduct { `Device' } -> `Int' #}
 {#fun libevdev_get_id_vendor as deviceVendor { `Device' } -> `Int' #}
 {#fun libevdev_get_id_bustype as deviceBustype { `Device' } -> `Int' #}
@@ -162,6 +177,12 @@
 {#fun libevdev_enable_event_code as enableCode { `Device', `Word16', `Word16', `Ptr ()' } -> `Errno' Errno #}
 {#fun libevdev_uinput_write_event as writeEvent { `UDevice', `Word16', `Word16', `Int32' } -> `Errno' Errno #}
 
+-- | LEDs values
+{#enum define LEDValue {
+    LIBEVDEV_LED_ON as LedOn,
+    LIBEVDEV_LED_OFF as LedOff}
+    deriving (Bounded, Eq, Ord, Read, Show) #}
+{#fun libevdev_kernel_set_led_value { `Device', convertEnum `LEDEvent', `LEDValue' } -> `Errno' Errno #}
 
 {- Util -}
 
diff --git a/src/Evdev/Uinput.hs b/src/Evdev/Uinput.hs
--- a/src/Evdev/Uinput.hs
+++ b/src/Evdev/Uinput.hs
@@ -6,9 +6,16 @@
     writeBatch,
     defaultDeviceOpts,
     DeviceOpts (..),
-    LL.AbsInfo (..),
     deviceSyspath,
     deviceDevnode,
+
+    -- * Re-exports from 'Evdev'
+    AbsInfo (..),
+    Event(..),
+    EventData(..),
+    KeyEvent(..),
+    EventCode(..),
+    EventValue(..),
 ) where
 
 import Control.Monad
diff --git a/test/Test.hs b/test/Test.hs
--- a/test/Test.hs
+++ b/test/Test.hs
@@ -42,6 +42,8 @@
             Nothing -> assertFailure "Couldn't find device with correct name"
             Just d -> do
                 putMVar start ()
+                (@?= Nothing) =<< devicePhys d
+                (@?= Nothing) =<< deviceUniq d
                 (@?= [EvSyn, EvKey]) =<< deviceEventTypes d
                 evs' <- whileJust ((\x -> guard (x /= last evs) $> x) . eventData <$> nextEvent d) pure
                 filter (/= SyncEvent SynReport) evs' @?= init evs
