我没有使用 Arduino 或 hArduino 的经验,所以对接下来的内容持保留态度。
鉴于每次都重新初始化董事会是不合理的reactimate
,我认为没有一个干净的选项[*]。根本问题是落实reactimate
在反应香蕉中不知道任何关于Arduino
monad,因此它添加的所有额外效果必须在此时已得到解决reactimate
触发该动作(因此IO
类型)。我能看到的唯一出路是推出你自己的版本withArduino
跳过初始化。快速浏览一下source https://hackage.haskell.org/package/hArduino-0.9/docs/src/System-Hardware-Arduino-Comm.html#withArduino,这看起来可行,尽管非常混乱。
[*] 或者至少是一个不涉及可变状态的干净选项,如正确的答案所示。
鉴于海因里希·阿普费尔穆斯(Heinrich Apfelmus)善意地通过提出一个有趣的出路来补充这个答案,我忍不住实施了他的建议。这也归功于 gelisam,因为他的答案的框架节省了我相当多的时间。除了代码块下方的注释之外,请参阅海因里希的博客 http://apfelmus.nfshost.com/blog/2012/06/07-forklift.html有关“叉车”的额外评论。
{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}
import Control.Monad (join, (<=<), forever)
import Control.Concurrent
import Data.Word
import Text.Printf
import Text.Read (readMaybe)
import Reactive.Banana
import Reactive.Banana.Frameworks
main :: IO ()
main = do
let inputPin = pin 1
outputPin = pin 2
readInputPin = digitalRead inputPin
copyPin = digitalWrite outputPin =<< readInputPin
ard <- newForkLift withArduino
(lineAddHandler, fireLine) <- newAddHandler
let networkDescription :: forall t. Frameworks t => Moment t ()
networkDescription = do
eLine <- fromAddHandler lineAddHandler
let eCopyPin = copyPin <$ filterE ("c" ==) eLine
eReadInputPin = readInputPin <$ filterE ("i" ==) eLine
reactimate $ (printf "Input pin is on? %s\n" . show <=< carry ard)
<$> eReadInputPin
reactimate $ carry ard
<$> eCopyPin
actuate =<< compile networkDescription
initialised <- newQSem 0
carry ard $ liftIO (signalQSem initialised)
waitQSem initialised
forever $ do
putStrLn "Enter c to copy, i to read input pin."
fireLine =<< getLine
-- Heinrich's forklift.
data ForkLift m = ForkLift { requests :: Chan (m ()) }
newForkLift :: MonadIO m
=> (m () -> IO ()) -> IO (ForkLift m)
newForkLift unlift = do
channel <- newChan
let loop = forever . join . liftIO $ readChan channel
forkIO $ unlift loop
return $ ForkLift channel
carry :: MonadIO m => ForkLift m -> m a -> IO a
carry forklift act = do
ref <- newEmptyMVar
writeChan (requests forklift) $ do
liftIO . putMVar ref =<< act
takeMVar ref
-- Mock-up lifted from gelisam's answer.
-- Please pretend that Arduino is abstract.
newtype Arduino a = Arduino { unArduino :: 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 p@(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?"
line <- getLine
case readMaybe line of
Just v -> return v
Nothing -> do
putStrLn "Bad read, retrying..."
unArduino $ digitalRead p
withArduino :: Arduino () -> IO ()
withArduino (Arduino body) = do
putStrLn "Pretend we're initializing the arduino."
body
Notes:
叉车(这里,ard
)运行一个Arduino
在单独的线程中循环。carry
允许我们发送Arduino
命令如readInputPin
and copyPin
通过a在这个线程中执行Chan (Arduino ())
.
它只是一个名字,但无论如何它的论点newForkLift
被叫unlift
很好地反映了上面的讨论。
通信是双向的。carry
crafts MVar
s 使我们能够访问由Arduino
命令。这使我们能够使用类似的事件eReadInputPin
以完全自然的方式。
各层完全分离。一方面,主循环仅触发 UI 事件,例如eLine
,然后由事件网络处理。另一方面,Arduino
代码仅通过叉车与事件网络和主循环进行通信。
为什么我放了一个信号 https://hackage.haskell.org/package/base-4.8.1.0/docs/Control-Concurrent-QSem.html在那里?我会让你猜猜如果你把它脱下来会发生什么......