8

在响应式香蕉中,我尝试使用hArduino 包中的reactimate :: Event (IO ()) -> Moment ()一些操作运行,这是. 包装中似乎没有提供的功能。您将如何执行操作?ArduinoMonadIOArduino a -> IO aArduinoreactimate

4

2 回答 2

4

我没有使用 Arduino 或 hArduino 的经验,因此请注意以下内容。

鉴于在每个 上重新初始化板是不合理的reactimate,我认为没有一个干净的选项 [*]。根本问题是,reactimate在响应式香蕉中的实现对 monad 一无所知Arduino,因此它添加的所有额外效果必须在reactimate触发动作(因此IO类型)时解决。我能看到的唯一出路是滚动你自己的版本withArduino,跳过初始化。快速浏览一下源代码,这看起来是可行的,如果非常混乱的话。

[*] 或者至少是一个不涉及可变状态的干净选项,如正确答案中所示。


鉴于 Heinrich Apfelmus 通过提出一个有趣的出路来增加这个答案,我忍不住实施了他的建议。也归功于 gelisam,因为他的答案的脚手架为我节省了很多时间。除了代码块下方的注释之外,请参阅Heinrich 的博客以获取有关“叉车”的更多评论。

{-# 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

笔记:

  • 叉车(此处为)在单独的线程中ard运行一个循环。允许我们通过. _ArduinocarryArduinoreadInputPincopyPinChan (Arduino ())

  • 它只是一个名字,但无论如何,newForkLift被称为的论点unlift很好地反映了上面的讨论。

  • 通信是双向的。carrycraftsMVar让我们可以访问Arduino命令返回的值。这使我们能够eReadInputPin以完全自然的方式使用事件。

  • 这些层被干净地分开。一方面,主循环仅触发 UI 事件eLine,然后由事件网络处理。另一方面,Arduino代码只通过叉车与事件网络和主循环通信。

  • 为什么我在里面放了一个sempahore?我会让你猜猜如果你把它取下来会发生什么......

于 2015-07-29T00:39:43.870 回答
2

您将如何执行 Arduino 操作reactimate

我会通过执行具有可观察到的副作用的 IO 操作来间接执行它们。然后,在里面withArduino,我会观察这个副作用并运行相应的 Arduino 命令。

这是一些示例代码。首先,让我们把进口商品排除在外。

{-# LANGUAGE GeneralizedNewtypeDeriving, ScopedTypeVariables #-}

import Control.Monad.IO.Class
import Data.IORef
import Data.Word
import Reactive.Banana
import Reactive.Banana.Frameworks
import Text.Printf

由于我没有 arduino,我将不得不从 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

在其余代码中,我将假设 Arduino 和 Pin 类型是不透明的。

我们需要一个事件网络来将表示从 arduino 接收到的信号的输入事件转换为描述我们想要发送到 arduino 的输出事件。为了让事情变得非常简单,让我们从一个引脚接收数据并在另一个引脚上输出完全相同的数据。

eventNetwork :: forall t. Event t Bool -> Event t Bool
eventNetwork = id

接下来,让我们将我们的事件网络连接到外部世界。当输出事件发生时,我只需将值写入 IORef,稍后我将能够观察到。

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

请注意在主循环之外如何reactimatecompile只调用一次。这些函数设置您的事件网络,您不想在每个循环中调用它们。

最后,我们运行主循环。

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

请注意我们如何liftIO从 Arduino 计算内部与事件网络进行交互。我们调用fireInputPin来触发一个输入事件,事件网络导致一个输出事件被触发作为响应,并且writeIORef我们给了 toreactimate导致输出事件的值被写入 IORef。如果事件网络更复杂,输入事件没有触发任何输出事件,IORef 的内容将保持不变。无论如何,我们可以观察这些内容,并使用它来确定要运行哪个 Arduino 计算。在这种情况下,我们只需将输出值发送到预定的引脚。

于 2015-07-30T05:40:50.203 回答