In reactive-banana, I am trying to run reactimate :: Event (IO ()) -> Moment ()
with some actions of Arduino
in hArduino package, an instance of MonadIO
. There seems no function of Arduino a -> IO a
provided in the package. How would you execute Arduino
actions in reactimate
?
How would you execute Arduino actions in
reactimate
?
I would cause them to be executed indirectly, by executing an IO action which has an observable side-effect. Then, inside withArduino
, I would observe this side-effect and run the corresponding Arduino command.
Here's some example code. First, let's get the imports out of the way.
{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
import Control.Monad.IO.Class
import Data.IORef
import Data.Word
import Reactive.Banana
import Reactive.Banana.Frameworks
import Text.Printf
Since I do not have an arduino, I'll have to mock up a few methods from hArduino.
newtype Arduino a = Arduino (IO a)
deriving (Functor, Applicative, Monad, MonadIO)
newtype Pin = Pin Word8
pin :: Word8 -> Pin
pin = Pin
digitalWrite :: Pin -> Bool -> Arduino ()
digitalWrite (Pin n) v = Arduino $ do
printf "Pretend pin %d on the arduino just got turned %s.\n"
n (if v then "on" else "off")
digitalRead :: Pin -> Arduino Bool
digitalRead (Pin n) = Arduino $ do
printf "We need to pretend we read a value from pin %d.\n" n
putStrLn "Should we return True or False?"
readLn
withArduino :: Arduino () -> IO ()
withArduino (Arduino body) = do
putStrLn "Pretend we're initializing the arduino."
body
In the rest of the code, I'll pretend that the Arduino and Pin types are opaque.
We'll need an event network to transform input events representing signals received from the arduino into output events describing what we want to send to the arduino. To keep things extremely simple, let's receive data from one pin and output the exact same data on another pin.
eventNetwork :: forall t. Event t Bool -> Event t Bool
eventNetwork = id
Next, let's connect our event network to the external world. When output events occur, I simply write the value into an IORef, which I'll be able to observe later.
main :: IO ()
main = do
(inputPinAddHandler, fireInputPin) <- newAddHandler
outputRef <- newIORef False
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
-- input
inputPinE <- fromAddHandler inputPinAddHandler
-- output
let outputPinE = eventNetwork inputPinE
reactimate $ writeIORef outputRef <$> outputPinE
network <- compile networkDescription
actuate network
withArduino $ do
let inputPin = pin 1
let outputPin = pin 2
-- initialize pins here...
-- main loop
loop inputPin outputPin fireInputPin outputRef
Note how reactimate
and compile
are only called once, outside the main loop. Those functions setup your event network, you do not want to call them on every loop.
Finally, we run the main loop.
loop :: Pin
-> Pin
-> (Bool -> IO ())
-> IORef Bool
-> Arduino ()
loop inputPin outputPin fireInputPin outputRef = do
-- read the input from the arduino
inputValue <- digitalRead inputPin
-- send the input to the event network
liftIO $ fireInputPin inputValue
-- read the output from the event network
outputValue <- liftIO $ readIORef outputRef
-- send the output to the arduino
digitalWrite outputPin outputValue
loop inputPin outputPin fireInputPin outputRef
Note how we use liftIO
to interact with the event network from inside an Arduino computation. We call fireInputPin
to trigger an input event, the event network causes an output event to be triggered in response, and the writeIORef
we gave to reactimate
causes the output event's value to be written to the IORef. If the event network was more complicated and the input event did not trigger any output event, the contents of the IORef would remain unchanged. Regardless, we can observe that contents, and use it to determine which Arduino computation to run. In this case, we simply send the output value to a predetermined pin.