diff options
author | Paul Oliver <contact@pauloliver.dev> | 2025-01-27 14:51:28 +0000 |
---|---|---|
committer | Paul Oliver <contact@pauloliver.dev> | 2025-01-29 04:12:57 +0000 |
commit | 9d2f95bb58f856aaf9142426e90e5783c98af8f1 (patch) | |
tree | 9453d5cbed1ae2165e6c8f0824ded00d2372e7bd | |
parent | 9310ba7e17f97c4570dce33552b3605155ca5c0c (diff) |
Makes doc-comments cleaner/more elegant
-rw-r--r-- | README.md | 52 | ||||
-rw-r--r-- | hsm-command/Main.hs | 5 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Fsm.hs | 5 | ||||
-rw-r--r-- | hsm-core/Hsm/Core/Log.hs | 5 | ||||
-rw-r--r-- | hsm-dummy-blinker/Main.hs | 5 | ||||
-rw-r--r-- | hsm-dummy-fail/Main.hs | 11 | ||||
-rw-r--r-- | hsm-dummy-gradient/Main.hs | 6 | ||||
-rw-r--r-- | hsm-dummy-poller/Main.hs | 5 | ||||
-rw-r--r-- | hsm-dummy-pulser/Main.hs | 4 | ||||
-rw-r--r-- | hsm-dummy-receiver/Main.hs | 5 | ||||
-rw-r--r-- | hsm-gpio/Hsm/GPIO.hs | 16 | ||||
-rw-r--r-- | hsm-pwm/Hsm/PWM.hs | 21 | ||||
-rw-r--r-- | hsm-status/Main.hs | 4 | ||||
-rw-r--r-- | udev/98-gpiod.rules | 2 | ||||
-rw-r--r-- | udev/99-pwm.rules | 10 |
15 files changed, 82 insertions, 74 deletions
@@ -1,26 +1,31 @@ # HsMouse -Experimental control code for robotics. Tested on Raspberry Pi 5. +Experimental control software for robotics, tested on Raspberry Pi 5. -## Features -- [`zeromq4-haskell`](https://hackage.haskell.org/package/zeromq4-haskell) -library is used for IPC. -- [`effectful`](https://hackage.haskell.org/package/effectful) library is used -to constrain effects within monadic computations. -- [`streamly`](https://hackage.haskell.org/package/streamly) library is used -to build pipelines modularly and stream data within pipeline elements. E.g. -`zmq client & processor & zmq server`. +## Features: +- Uses the +[`zeromq4-haskell`](https://hackage.haskell.org/package/zeromq4-haskell) +library for inter-process communication (IPC). +- The [`effectful`](https://hackage.haskell.org/package/effectful) library is +employed to constrain effects within monadic computations. +- The [`streamly`](https://hackage.haskell.org/package/streamly) library is +used to build modular data pipelines and stream data between pipeline elements +(e.g., `zmq client & processor & zmq server`). -## Build -Install [`stack`](https://docs.haskellstack.org/en/stable/). I recommend using -[`ghcup`](https://www.haskell.org/ghcup/) for this. Run `stack build` to -compile all libraries and executables. Note: you might need to install some -system dependencies on your host first (e.g. `libzmq`, `libgpiod`, etc.) +## Build Instructions: +1. Install [`stack`](https://docs.haskellstack.org/en/stable/). It’s +recommended to use [`ghcup`](https://www.haskell.org/ghcup/) for installation. +2. Run `stack build` to compile the libraries and executables. -## Test -On one terminal, run `stack exec dummy-receiver`. This will initialize a ZMQ -client that will wait for incoming pulses. On a separate terminal, run -`stack exec dummy-pulser`. You should be able to see pulses being transmitted -from server to client. E.g.: +> Note: You may need to install system dependencies on your host first (e.g., +> `libzmq`, `libgpiod`, etc.). + +## Testing the Application +1. In one terminal, run `stack exec dummy-receiver` to start a ZMQ client that +waits for incoming pulses. +2. In another terminal, run `stack exec dummy-pulser` to send pulses to the +client. + +You should see the following logs: ``` $> stack exec dummy-receiver 2025-01-12 21:27:02 INFO receiver/client: Initializing ZMQ client @@ -43,7 +48,8 @@ $> stack exec dummy-pulser 2025-01-12 21:27:25 ATTENTION pulser/fsm: No state returned, exiting FSM ``` -## GPIO and PWM without root -On the Pi 5, copy files in `./udev` into `/etc/udev/rules.d`. These rules -allow the `gpio` and `pwm` user groups to interface with GPIO and PWM -subsystems respectively. +## GPIO and PWM Access Without Root: +To enable GPIO and PWM access without root privileges on the Raspberry Pi 5, +copy the files from the `./udev` directory into `/etc/udev/rules.d`. These +rules grant the `gpio` and `pwm` user groups permission to interface with the +respective subsystems. diff --git a/hsm-command/Main.hs b/hsm-command/Main.hs index d135e53..3d06518 100644 --- a/hsm-command/Main.hs +++ b/hsm-command/Main.hs @@ -21,8 +21,9 @@ data Env = Env $(deriveFromYaml ''Env) --- Command Service: --- Reads movement commands from the terminal and publishes them through ZMQ. +-- Command Service: Reads movement commands from the user via the terminal, +-- using an interface similar to GNU Readline. The parsed commands are then +-- published through ZMQ. main :: IO () main = launch @Env "command" id $ \env logger level -> diff --git a/hsm-core/Hsm/Core/Fsm.hs b/hsm-core/Hsm/Core/Fsm.hs index d1c2f5d..b04cca3 100644 --- a/hsm-core/Hsm/Core/Fsm.hs +++ b/hsm-core/Hsm/Core/Fsm.hs @@ -26,8 +26,9 @@ data FsmOutput i o env sta = data FsmResult i o env sta = FsmResult o sta (FsmState i o env sta) --- Finite state machines allow processing of stream elements using pure --- functions. One or more FSMs can be included within a `Streamly` pipeline. +-- Finite State Machine (FSM): Implements a finite state machine for +-- processing individual stream elements using pure functions. Multiple FSMs +-- can be included as elements in a `Streamly` pipeline. fsm :: forall i o env sta es. ( Log :> es diff --git a/hsm-core/Hsm/Core/Log.hs b/hsm-core/Hsm/Core/Log.hs index 6930e90..c097e0f 100644 --- a/hsm-core/Hsm/Core/Log.hs +++ b/hsm-core/Hsm/Core/Log.hs @@ -11,8 +11,9 @@ import Effectful (Eff, (:>)) import Effectful.Dispatch.Static (unsafeEff_) import Effectful.Log qualified as L --- Helper function allows logging within IO, Useful during `resourcet` --- allocation and release operations. +-- This helper function enables logging within the IO monad, which is +-- particularly useful during resource allocation and release operations with +-- `resourcet`. withLogIO :: L.Log :> es => Eff es (L.LogLevel -> Text -> IO ()) withLogIO = do logIO <- L.getLoggerIO diff --git a/hsm-dummy-blinker/Main.hs b/hsm-dummy-blinker/Main.hs index 9405ae3..5c7cb13 100644 --- a/hsm-dummy-blinker/Main.hs +++ b/hsm-dummy-blinker/Main.hs @@ -50,9 +50,8 @@ handle = S.fold S.drain . S.mapM handler env <- ask @Env toggle sta () [env.period, 0] --- Dummy blinker service: --- Proof of concept. This service toggles a GPIO on and off using a set --- period. +-- Dummy Blinker Service: A proof of concept that toggles a GPIO pin on and +-- off at a set interval. main :: IO () main = launch @Env "dummy-blinker" withoutInputEcho $ \env logger level -> diff --git a/hsm-dummy-fail/Main.hs b/hsm-dummy-fail/Main.hs index 785304c..4844f3b 100644 --- a/hsm-dummy-fail/Main.hs +++ b/hsm-dummy-fail/Main.hs @@ -30,15 +30,16 @@ singleError :: (Concurrent :> es, Reader Env :> es) => Stream (Eff es) ByteString singleError = fromEffect $ do - -- Seemingly, the service needs to be alive for a bit for ZMQ comms to - -- kick in. env <- ask @Env + -- The service needs to remain active for a short time to ensure the ZMQ + -- message is sent. Without this delay, the service may exit before + -- the communication occurs, preventing the message from being + -- transmitted. threadDelay $ fromIntegral env.alive return $ message env.name $ Error 0 "Sent from dummy-fail service" --- Dummy fail service: --- Proof of concept. Publishes a single error that can be catched by a --- listening client. +-- Dummy Fail Service: A proof of concept that publishes a single error +-- message, which can be caught by a listening client. main :: IO () main = launch @Env "dummy-fail" withoutInputEcho $ \env logger level -> diff --git a/hsm-dummy-gradient/Main.hs b/hsm-dummy-gradient/Main.hs index 05ead49..de43a8f 100644 --- a/hsm-dummy-gradient/Main.hs +++ b/hsm-dummy-gradient/Main.hs @@ -34,9 +34,9 @@ pwmLoop = do threadDelay $ fromIntegral env.stepDelay dutyCycle dc --- Dummy gradient service: --- Simple test for PWM control. Increases duty-cycle gradually on default PWM --- channel. +-- Dummy Gradient Service: A test for PWM control. Gradually increases the +-- duty cycle on the default PWM channel (GPIO pin 18), causing an LED to +-- brighten until it reaches max brightness, then turns off and repeats. main :: IO () main = launch @Env "dummy-gradient" withoutInputEcho $ \env logger level -> diff --git a/hsm-dummy-poller/Main.hs b/hsm-dummy-poller/Main.hs index 8e6908b..d3d2f88 100644 --- a/hsm-dummy-poller/Main.hs +++ b/hsm-dummy-poller/Main.hs @@ -45,9 +45,8 @@ handle = S.fold S.drain . S.mapM handler $ logInfo_ . mappend "Received pulse #" . pack . show . body @Word delay --- Dummy poller service: --- Proof of concept. Polls for "pulses" through ZMQ at a set interval and --- logs each time one is received. +-- Dummy Poller Service: A proof of concept that polls for "pulses" via ZMQ at +-- a set interval and logs a message each time one is received. main :: IO () main = launch @Env "dummy-poller" withoutInputEcho $ \env logger level -> diff --git a/hsm-dummy-pulser/Main.hs b/hsm-dummy-pulser/Main.hs index 3e2a7ae..fc4712c 100644 --- a/hsm-dummy-pulser/Main.hs +++ b/hsm-dummy-pulser/Main.hs @@ -42,8 +42,8 @@ stateRun = Nothing [(LogAttention, "Sent " <> pack (show env.pulses) <> " pulses")] --- Dummy pulser service: --- Proof of concept. Publishes a "pulse" through ZMQ at a set interval. +-- Dummy Pulser Service: A proof of concept that publishes a "pulse" message +-- via ZMQ at a set interval. main :: IO () main = launch @Env "dummy-pulser" withoutInputEcho $ \env logger level -> diff --git a/hsm-dummy-receiver/Main.hs b/hsm-dummy-receiver/Main.hs index 6fb78b1..02496c4 100644 --- a/hsm-dummy-receiver/Main.hs +++ b/hsm-dummy-receiver/Main.hs @@ -36,9 +36,8 @@ handle = S.fold S.drain . S.mapM handler . show . body @Word --- Dummy receiver service: --- Proof of concept. Listens for "pulses" through ZMQ and logs each time one --- is received. +-- Dummy Receiver Service: A proof of concept that listens for "pulses" via +-- ZMQ and logs a message each time one is received. main :: IO () main = launch @Env "dummy-receiver" withoutInputEcho $ \env logger level -> diff --git a/hsm-gpio/Hsm/GPIO.hs b/hsm-gpio/Hsm/GPIO.hs index 5357167..3c5e8fc 100644 --- a/hsm-gpio/Hsm/GPIO.hs +++ b/hsm-gpio/Hsm/GPIO.hs @@ -24,7 +24,6 @@ import GHC.Generics (Generic) import Hsm.Core.Log (flushLogger) import System.Process (callCommand) --- Monofunctional GPIO pins data GPIO = GPIO5 | GPIO6 @@ -42,10 +41,11 @@ data GPIOEffect key a b type instance DispatchOf (GPIOEffect key) = Static E.WithSideEffects --- Effect state is a mapping function from type `key` to a `Set` of GPIO pins. --- This enables `key`s of any type to control many pins simultaneously. Using --- a function (instead of `Data.Map`) ensures all keys map to pins, given the --- provided function is total. +-- The static representation of this effect is a function that maps a `key` to +-- a `Set` of GPIO pins. This allows a single `key` of any type to control +-- multiple pins simultaneously. By using a function (instead of `Data.Map`), +-- we ensure that the mapping is total, meaning every `key` will map to a +-- corresponding set of pins. newtype instance E.StaticRep (GPIOEffect (key :: Type)) = GPIOEffect (key -> Set GPIO) @@ -56,9 +56,9 @@ stateStr :: IsString a => Bool -> a stateStr True = "on" stateStr False = "off" --- To control the pins, I use a subprocess call to `gpioset`. In the future --- I'd prefer wrapping `libgpiod` directly. It looks like no one has created a --- C wrapper yet, I might do it if I get bored. :) +-- Currently, pin control is done via a subprocess call to `gpioset`. In the +-- future, I'd prefer to wrap `libgpiod` directly. It seems no C wrapper +-- exists yet, but I might create one. gpioset :: Log :> es => Bool -> Set GPIO -> [Word] -> Eff es () gpioset state gpios periods = do localDomain domain $ logTrace_ $ "Calling command: " <> pack command diff --git a/hsm-pwm/Hsm/PWM.hs b/hsm-pwm/Hsm/PWM.hs index a5e1d27..c7bb1e5 100644 --- a/hsm-pwm/Hsm/PWM.hs +++ b/hsm-pwm/Hsm/PWM.hs @@ -21,20 +21,19 @@ import GHC.Records (HasField) import System.FilePath ((</>)) import System.Process (callCommand) --- The following PWMEffect implementation assumes `dtoverlay=pwm` to be set on --- `/boot/config.txt`. On the Pi 5, this enables one active PWM on GPIO 18. --- This is channel 2, so the address attribute will be 2. Alternative --- configurations with more PWM channels are possible. Consult the following --- links for more info: +-- This `PWMEffect` implementation assumes `dtoverlay=pwm` is set in +-- `/boot/config.txt`, enabling PWM on GPIO 18 (channel 2) for the Pi 5. The +-- address attribute will be 2. Alternative configurations with additional PWM +-- channels are possible. For more information, consult the following links: -- --- Modifications to `config.txt`: --- https://www.pi4j.com/blog/2024/20240423_pwm_rpi5/#modify-configtxt +-- - Modifications to `config.txt`: +-- https://www.pi4j.com/blog/2024/20240423_pwm_rpi5/#modify-configtxt -- --- SysFS PWM interface: --- https://forums.raspberrypi.com/viewtopic.php?t=359251 +-- - SysFS PWM interface: +-- https://forums.raspberrypi.com/viewtopic.php?t=359251 -- --- UDEV setup for non-root access: --- https://forums.raspberrypi.com/viewtopic.php?t=316514 +-- - UDEV setup for non-root access: +-- https://forums.raspberrypi.com/viewtopic.php?t=316514 data PWMEffect a b type instance DispatchOf PWMEffect = Static E.WithSideEffects diff --git a/hsm-status/Main.hs b/hsm-status/Main.hs index 634d1d1..594661b 100644 --- a/hsm-status/Main.hs +++ b/hsm-status/Main.hs @@ -73,8 +73,8 @@ mapper :: Env -> Bool -> Set GPIO mapper env True = singleton env.gpioOk mapper env False = singleton env.gpioError --- Status service blinks a GPIO pin periodically and listens for error --- messages. If an error is received it switches to a different pin. +-- Status Service: Periodically blinks a GPIO pin and listens for error +-- messages. Upon receiving an error, it switches to a different pin. main :: IO () main = launch "status" withoutInputEcho $ \env logger level -> diff --git a/udev/98-gpiod.rules b/udev/98-gpiod.rules index 4961d33..01a05ed 100644 --- a/udev/98-gpiod.rules +++ b/udev/98-gpiod.rules @@ -1,2 +1,2 @@ -# Grants GPIO access to the gpio group. +# This rule grants the `gpio` group access to GPIO devices. SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="gpiod", MODE="0660" diff --git a/udev/99-pwm.rules b/udev/99-pwm.rules index 2b95f14..8407ebe 100644 --- a/udev/99-pwm.rules +++ b/udev/99-pwm.rules @@ -1,7 +1,9 @@ -# Grants PWM access to the pwm group. Because UDEV is async, there might be a -# slight delay between changes to the directory tree (e.g., a new PWM channel -# being added) and changes to permissions taking effect. The command -# `udevadm settle` can be used to wait for the rule to finish execution. +# This UDEV rule provides the `pwm` user group with access to PWM devices. +# Note that UDEV operates asynchronously, so there may be a slight delay +# between changes to the directory structure (e.g., when a new PWM channel is +# added) and the corresponding permission updates. To ensure the rule has been +# fully applied, you can use the command `udevadm settle` to wait for the UDEV +# process to complete. SUBSYSTEM=="pwm*", PROGRAM="/bin/sh -c ' \ chown -R root:pwm /sys/class/pwm ; \ chmod -R 770 /sys/class/pwm ; \ |