aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Oliver <contact@pauloliver.dev>2025-01-27 14:51:28 +0000
committerPaul Oliver <contact@pauloliver.dev>2025-01-29 04:12:57 +0000
commit9d2f95bb58f856aaf9142426e90e5783c98af8f1 (patch)
tree9453d5cbed1ae2165e6c8f0824ded00d2372e7bd
parent9310ba7e17f97c4570dce33552b3605155ca5c0c (diff)
Makes doc-comments cleaner/more elegant
-rw-r--r--README.md52
-rw-r--r--hsm-command/Main.hs5
-rw-r--r--hsm-core/Hsm/Core/Fsm.hs5
-rw-r--r--hsm-core/Hsm/Core/Log.hs5
-rw-r--r--hsm-dummy-blinker/Main.hs5
-rw-r--r--hsm-dummy-fail/Main.hs11
-rw-r--r--hsm-dummy-gradient/Main.hs6
-rw-r--r--hsm-dummy-poller/Main.hs5
-rw-r--r--hsm-dummy-pulser/Main.hs4
-rw-r--r--hsm-dummy-receiver/Main.hs5
-rw-r--r--hsm-gpio/Hsm/GPIO.hs16
-rw-r--r--hsm-pwm/Hsm/PWM.hs21
-rw-r--r--hsm-status/Main.hs4
-rw-r--r--udev/98-gpiod.rules2
-rw-r--r--udev/99-pwm.rules10
15 files changed, 82 insertions, 74 deletions
diff --git a/README.md b/README.md
index 58d4bbf..993da8b 100644
--- a/README.md
+++ b/README.md
@@ -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 ; \