diff options
Diffstat (limited to 'hsm-pwm/Hsm')
| -rw-r--r-- | hsm-pwm/Hsm/PWM.hs | 58 | 
1 files changed, 42 insertions, 16 deletions
| diff --git a/hsm-pwm/Hsm/PWM.hs b/hsm-pwm/Hsm/PWM.hs index c7bb1e5..afc13c4 100644 --- a/hsm-pwm/Hsm/PWM.hs +++ b/hsm-pwm/Hsm/PWM.hs @@ -11,15 +11,17 @@ module Hsm.PWM    , runPWM    ) where +import Control.Monad.Loops (untilM_)  import Data.Text (Text, pack)  import Effectful (Dispatch(Static), DispatchOf, Eff, IOE, (:>)) +import Effectful.Concurrent (Concurrent, threadDelay)  import Effectful.Dispatch.Static qualified as E  import Effectful.Exception (bracket_)  import Effectful.Log (Log, localDomain, logTrace_)  import Effectful.Reader.Static (Reader, ask)  import GHC.Records (HasField)  import System.FilePath ((</>)) -import System.Process (callCommand) +import System.Posix.Files (fileAccess)  -- This `PWMEffect` implementation assumes `dtoverlay=pwm` is set in  -- `/boot/config.txt`, enabling PWM on GPIO 18 (channel 2) for the Pi 5. The @@ -47,6 +49,12 @@ domain = "pwm"  pwmchip :: FilePath  pwmchip = "/sys/class/pwm/pwmchip0" +exportPath :: FilePath +exportPath = pwmchip </> "export" + +unexportPath :: FilePath +unexportPath = pwmchip </> "unexport" +  chanIdx :: Word  chanIdx = 2 @@ -56,14 +64,30 @@ chanPath = pwmchip </> ("pwm" <> show chanIdx)  enablePath :: FilePath  enablePath = chanPath </> "enable" -dutyCycle :: (Log :> es, PWMEffect :> es) => Word -> Eff es () +periodPath :: FilePath +periodPath = chanPath </> "period" + +dutyCyclePath :: FilePath +dutyCyclePath = chanPath </> "duty_cycle" + +-- This function waits for a file at the given `path` to become writable by +-- the `pwm` user group. A UDEV rule ensures that files in +-- `/sys/class/pwm/pwmchip*` are made writable through a `chown` call. +-- However, because UDEV rules are applied asynchronously, there may be a +-- brief delay before the rule takes effect. This function blocks and +-- repeatedly checks the file's write permissions by calling `fileAccess`. It +-- continues checking until write access is confirmed. +waitWritable :: (Concurrent :> es, Log :> es) => FilePath -> Eff es () +waitWritable path = do +  logTrace_ $ "Waiting for " <> pack path <> " to become writable" +  untilM_ (threadDelay 1000) $ E.unsafeEff_ (fileAccess path False True False) + +dutyCycle :: (Concurrent :> es, Log :> es, PWMEffect :> es) => Word -> Eff es ()  dutyCycle dc = do    localDomain domain $ logTrace_ $ "Setting duty cycle to " <> pack (show dc) -  E.unsafeEff_ $ writeFile dutyCyclePath $ show dc -  where -    dutyCyclePath = chanPath </> "duty_cycle" +  E.unsafeEff_ $ writeFile (chanPath </> "duty_cycle") $ show dc -disable :: (Log :> es, PWMEffect :> es) => Eff es () +disable :: (Concurrent :> es, Log :> es, PWMEffect :> es) => Eff es ()  disable = do    dutyCycle 0    localDomain domain @@ -71,7 +95,8 @@ disable = do      $ "Disabling PWM channel " <> pack (show chanIdx)    E.unsafeEff_ $ writeFile enablePath "0" -withPWM :: (Log :> es, PWMEffect :> es) => Eff es a -> Eff es a +withPWM :: +     (Concurrent :> es, Log :> es, PWMEffect :> es) => Eff es a -> Eff es a  withPWM = bracket_ activate disable    where      activate = do @@ -83,30 +108,31 @@ withPWM = bracket_ activate disable  runPWM ::       forall env es a.       ( HasField "pwmPeriod" env Word +     , Concurrent :> es       , IOE :> es       , Log :> es       , Reader env :> es       )    => Eff (PWMEffect : es) a    -> Eff es a -runPWM action = E.evalStaticRep (PWMEffect ()) $ bracket_ acquire release action +runPWM = E.evalStaticRep (PWMEffect ()) . bracket_ acquire release    where -    export = pwmchip </> "export" -    period = chanPath </> "period" -    unexport = pwmchip </> "unexport"      acquire = do        localDomain domain $ do +        waitWritable exportPath +        waitWritable unexportPath          logTrace_            $ "Exporting channel "                <> pack (show chanIdx)                <> " on chip "                <> pack pwmchip -        E.unsafeEff_ $ writeFile export $ show chanIdx -        logTrace_ "Giving UDEV a chance to settle" -        E.unsafeEff_ $ callCommand "udevadm settle" +        E.unsafeEff_ $ writeFile exportPath $ show chanIdx +        waitWritable enablePath +        waitWritable periodPath +        waitWritable dutyCyclePath          env <- ask @env          logTrace_ $ "Fixing period to " <> pack (show env.pwmPeriod) -        E.unsafeEff_ $ writeFile period $ show env.pwmPeriod +        E.unsafeEff_ $ writeFile periodPath $ show env.pwmPeriod        disable      release = do        disable @@ -116,4 +142,4 @@ runPWM action = E.evalStaticRep (PWMEffect ()) $ bracket_ acquire release action              <> pack (show chanIdx)              <> " on chip "              <> pack pwmchip -      E.unsafeEff_ $ writeFile unexport $ show chanIdx +      E.unsafeEff_ $ writeFile unexportPath $ show chanIdx | 
