packages feed

hArduino 0.2 → 0.3

raw patch · 21 files changed

+1183/−298 lines, 21 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- System.Hardware.Arduino.Examples.Blink: blink :: IO ()
- System.Hardware.Arduino.Examples.Switch: switch :: IO ()
+ System.Hardware.Arduino: analogRead :: Pin -> Arduino Int
+ System.Hardware.Arduino: data Pin
+ System.Hardware.Arduino: pullUpResistor :: Pin -> Bool -> Arduino ()
+ System.Hardware.Arduino: setAnalogSamplingInterval :: Int -> Arduino ()
+ System.Hardware.Arduino: waitAny :: [Pin] -> Arduino [Bool]
+ System.Hardware.Arduino: waitAnyHigh :: [Pin] -> Arduino [Bool]
+ System.Hardware.Arduino: waitAnyLow :: [Pin] -> Arduino [Bool]
+ System.Hardware.Arduino.LCD: Hitachi44780 :: Pin -> Pin -> Pin -> Pin -> Pin -> Pin -> Int -> Int -> Bool -> LCDController
+ System.Hardware.Arduino.LCD: data LCDController
+ System.Hardware.Arduino.LCD: data LCDSymbol
+ System.Hardware.Arduino.LCD: dotMode5x10 :: LCDController -> Bool
+ System.Hardware.Arduino.LCD: lcdAutoScrollOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdAutoScrollOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdBlinkOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdBlinkOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdClear :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdCols :: LCDController -> Int
+ System.Hardware.Arduino.LCD: lcdCreateSymbol :: LCD -> [String] -> Arduino LCDSymbol
+ System.Hardware.Arduino.LCD: lcdCursorOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdCursorOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdD4 :: LCDController -> Pin
+ System.Hardware.Arduino.LCD: lcdD5 :: LCDController -> Pin
+ System.Hardware.Arduino.LCD: lcdD6 :: LCDController -> Pin
+ System.Hardware.Arduino.LCD: lcdD7 :: LCDController -> Pin
+ System.Hardware.Arduino.LCD: lcdDisplayOff :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdDisplayOn :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdEN :: LCDController -> Pin
+ System.Hardware.Arduino.LCD: lcdFlash :: LCD -> Int -> Int -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdHome :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdInternalSymbol :: Word8 -> LCDSymbol
+ System.Hardware.Arduino.LCD: lcdLeftToRight :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdRS :: LCDController -> Pin
+ System.Hardware.Arduino.LCD: lcdRegister :: LCDController -> Arduino LCD
+ System.Hardware.Arduino.LCD: lcdRightToLeft :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdRows :: LCDController -> Int
+ System.Hardware.Arduino.LCD: lcdScrollDisplayLeft :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdScrollDisplayRight :: LCD -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdSetCursor :: LCD -> (Int, Int) -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdWrite :: LCD -> String -> Arduino ()
+ System.Hardware.Arduino.LCD: lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino ()
+ System.Hardware.Arduino.SamplePrograms.Analog: analogVal :: IO ()
+ System.Hardware.Arduino.SamplePrograms.Blink: blink :: IO ()
+ System.Hardware.Arduino.SamplePrograms.Button: button :: IO ()
+ System.Hardware.Arduino.SamplePrograms.Counter: counter :: IO ()
+ System.Hardware.Arduino.SamplePrograms.LCD: happy :: [String]
+ System.Hardware.Arduino.SamplePrograms.LCD: hitachi :: LCDController
+ System.Hardware.Arduino.SamplePrograms.LCD: lcdDemo :: IO ()
+ System.Hardware.Arduino.SamplePrograms.LCD: sad :: [String]

Files

+ CHANGES.md view
@@ -0,0 +1,30 @@+* Hackage: (http://hackage.haskell.org/package/hArduino)+* GitHub:  (http://leventerkok.github.com/hArduino)++* Latest Hackage released version: 0.3++### Version 0.3, 2013-02-10++ * Library+    * Add support for pull-up resistors+    * Implement routines for waiting on digital triggers+    * Add support for reading analog values and setting sampling frequency.+    * Add support for LCDs (based on the Hitachi 44780 chip)+    * Better handling for Ctrl-C interrupts+ * Examples+    * Add counter: use push buttons to count up and down+    * Add analog-reading example+    * Add LCD controller example+    * Add wiring schematics for all sample programs++### Version 0.2, 2013-01-28++ * Rewrite the communication engine+ * Digital input/output implementation+ * Add switch example++### Version 0.1, 2013-01-14++ * Initial design+ * Blink example operational+ * Created home page at: http://leventerkok.github.com/hArduino 
INSTALL view
@@ -1,9 +1,4 @@-The usbArduino library can be installed simply by issuing cabal install+The hArduino library can be installed simply by issuing cabal install like this: -     cabal install sbv--The package depends on the usb library itself, which in turn relies on-the availability of the libusb package on your platform. See notes-at https://github.com/basvandijk/bindings-libusb for details on how-to get the latter.+     cabal install hArduino
− README
@@ -1,1 +0,0 @@-Please see http://leventerkok.github.com/hArduino.
+ README.md view
@@ -0,0 +1,1 @@+Please see http://leventerkok.github.com/hArduino.
− RELEASENOTES
@@ -1,18 +0,0 @@-Hackage: http://hackage.haskell.org/package/hArduino-GitHub:  http://leventerkok.github.com/hArduino --Latest Hackage released version: 0.2--======================================================================-Version 0.2, 2013-01-28-- - Rewrite the communication engine- - Digital input/output implementation- - Add switch example--======================================================================-Version 0.1, 2013-01-14-- - Initial design- - Blink example operational- - Home page at: http://leventerkok.github.com/hArduino 
System/Hardware/Arduino.hs view
@@ -17,6 +17,9 @@ -- Arduino! It simply allows you to control a board from Haskell, where you -- can exchange information with the board, send/receive commands from other -- peripherals connected, etc.+--+-- See <http://www.youtube.com/watch?v=PPa3im44t2g> for a short video (4m29s)+-- of the blink example. ------------------------------------------------------------------------------- module System.Hardware.Arduino (   -- * Running the controller@@ -24,15 +27,20 @@   -- * Programming the Arduino   -- ** Basic handshake with the board   , queryFirmware-  -- ** Controlling the pins-  , setPinMode-  -- ** Reading and Writing digital values-  , digitalRead, digitalWrite-  -- ** Misc utilities-  , waitFor, delay-  -- * Hardware components on the board-  -- ** Pins-  , pin, PinMode(..)+  -- ** Accessing pins+  , pin, Pin, PinMode(..), setPinMode+  -- ** Digital I/O+  -- *** Writing digital values+  , digitalWrite+  -- *** Reading digital values+  , digitalRead,  pullUpResistor, waitFor, waitAny, waitAnyHigh, waitAnyLow+  -- ** Analog Communication+  -- *** Setting up sampling interval+  , setAnalogSamplingInterval+  -- *** Reading analog values+  , analogRead+  -- * Misc utilities+  , delay  )  where 
System/Hardware/Arduino/Comm.hs view
@@ -9,18 +9,23 @@ -- Basic serial communication routines ------------------------------------------------------------------------------- -{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE NamedFieldPuns      #-}+{-# LANGUAGE ScopedTypeVariables #-} module System.Hardware.Arduino.Comm where  import Control.Monad        (when, forever)-import Control.Concurrent   (myThreadId, throwTo, newChan, newMVar, putMVar, writeChan, readChan, forkIO, modifyMVar_)-import Control.Exception    (tryJust, AsyncException(UserInterrupt))+import Control.Concurrent   (MVar, myThreadId, ThreadId, throwTo, newChan, newMVar, newEmptyMVar, putMVar, writeChan, readChan, forkIO, modifyMVar_, tryTakeMVar, killThread)+import Control.Exception    (tryJust, AsyncException(UserInterrupt), handle, SomeException) import Control.Monad.State  (runStateT, gets, liftIO, modify)-import Data.Bits            (testBit)+import Data.Bits            (testBit, (.&.))+import Data.List            (intercalate)+import Data.Maybe           (listToMaybe) import System.Posix.Signals (installHandler, keyboardSignal, Handler(Catch))+import System.Timeout       (timeout)+import System.IO            (stderr, hPutStrLn)  import qualified Data.ByteString            as B (unpack, length)-import qualified Data.Map                   as M (empty, mapWithKey)+import qualified Data.Map                   as M (empty, mapWithKey, insert, assocs, lookup) import qualified Data.Set                   as S (empty) import qualified System.Hardware.Serialport as S (withSerial, defaultSerialSettings, CommSpeed(CS57600), commSpeed, recv, send) @@ -49,31 +54,51 @@            _ <- installHandler keyboardSignal (Catch (throwTo tid UserInterrupt)) Nothing            debugger <- mkDebugPrinter verbose            debugger $ "Accessing arduino located at: " ++ show fp-           let Arduino controller = do initialize+           listenerTid <- newEmptyMVar+           let Arduino controller = do initOK <- initialize listenerTid+                                       if initOK+                                          then program+                                          else error "Communication time-out (5s) expired."                                        program-           S.withSerial fp S.defaultSerialSettings{S.commSpeed = S.CS57600} $ \port -> do+           handle (\(e::SomeException) -> do cleanUp listenerTid+                                             hPutStrLn stderr $ "*** hArduino:ERROR: " ++ show e+                                                              ++ concatMap ("\n*** " ++) [ "Make sure your Arduino is connected to " ++ fp+                                                                                         , "And StandardFirmata is running on it!"+                                                                                         ]) $+             S.withSerial fp S.defaultSerialSettings{S.commSpeed = S.CS57600} $ \port -> do                 let initBoardState = BoardState {-                                         analogReportingPins  = S.empty+                                         boardCapabilities    = BoardCapabilities M.empty+                                       , analogReportingPins  = S.empty                                        , digitalReportingPins = S.empty                                        , pinStates            = M.empty                                        , digitalWakeUpQueue   = []+                                       , lcds                 = M.empty                                      }                 bs <- newMVar initBoardState                 dc <- newChan                 let initState = ArduinoState {                                    message       = debugger+                                 , bailOut       = bailOut listenerTid                                  , port          = port                                  , firmataID     = "Unknown"                                  , capabilities  = BoardCapabilities M.empty                                  , boardState    = bs                                  , deviceChannel = dc+                                 , listenerTid   = listenerTid                               }                 res <- tryJust catchCtrlC $ runStateT controller initState                 case res of                   Left () -> putStrLn "hArduino: Caught Ctrl-C, quitting.."                   _       -> return ()+                cleanUp listenerTid  where catchCtrlC UserInterrupt = Just ()        catchCtrlC _             = Nothing+       cleanUp tid = do mbltid <- tryTakeMVar tid+                        case mbltid of+                          Just t -> killThread t+                          _      -> return ()+       bailOut tid m ms = do cleanUp tid+                             error $ "\n*** hArduino:ERROR: " ++ intercalate "\n*** " (m:ms)  -- | Send down a request. send :: Request -> Arduino ()@@ -90,10 +115,14 @@ recv = do ch <- gets deviceChannel           liftIO $ readChan ch +-- | Receive a sys-ex response with time-out. This is a blocking call, and will wait until+-- either the time-out expires or the message is received+recvTimeOut :: Int -> Arduino (Maybe Response)+recvTimeOut n = do ch <- gets deviceChannel+                   liftIO $ timeout n (readChan ch)+ -- | Start a thread to listen to the board and populate the channel with incoming queries.--- NB. This function is run in a thread; so be careful not to throw error or die otherwise--- in here.-setupListener :: Arduino ()+setupListener :: Arduino ThreadId setupListener = do         serial <- gets port         dbg    <- gets message@@ -118,7 +147,20 @@                            Right nonSysEx    -> unpackageNonSysEx getBytes nonSysEx                 case resp of                   Unimplemented{}      -> dbg $ "Ignoring the received response: " ++ show resp-                  DigitalMessage p l h -> do dbg $ "Updating port " ++ show p ++ " values with " ++ showByteList [l,h]+                  AnalogMessage mp l h -> modifyMVar_ bs $ \bst ->+                                           do -- the mp is indexed at 0; need to find the mapping+                                              let BoardCapabilities caps = boardCapabilities bst+                                                  mbP = listToMaybe [mappedPin | (mappedPin, (Just mp', _)) <- M.assocs caps, pinNo mp == mp']+                                              case mbP of+                                                Nothing -> return bst -- Mapping hasn't happened yet+                                                Just p  -> do+                                                   let v = (128 * fromIntegral (h .&. 0x07) + fromIntegral (l .&. 0x7f)) :: Int+                                                   case pinValue `fmap` (p `M.lookup` pinStates bst) of+                                                     Just (Just (Right v'))+                                                       | abs (v - v') < 10  -> return () -- be quiet, otherwise prints too much+                                                     _                      -> dbg $ "Updating analog pin " ++ show p ++ " values with " ++ showByteList [l,h] ++ " (" ++ show v ++ ")"+                                                   return bst{ pinStates = M.insert p PinData{pinMode = ANALOG, pinValue = Just (Right v)} (pinStates bst) }+                  DigitalMessage p l h -> do dbg $ "Updating digital port " ++ show p ++ " values with " ++ showByteList [l,h]                                              modifyMVar_ bs $ \bst -> do                                                   let upd o od | p /= pinPort o               = od   -- different port, no change                                                                | pinMode od `notElem` [INPUT] = od   -- not an input pin, ignore@@ -137,37 +179,55 @@         bs <- gets boardState         tid <- liftIO $ forkIO $ forever (listener bs)         debug $ "Started listener thread: " ++ show tid+        return tid --- | Initialize our board, get capabilities, etc-initialize :: Arduino ()-initialize = do-     -- Step 1: Set up the listener thread-     setupListener+-- | Initialize our board, get capabilities, etc. Returns True if initialization+-- went OK, False if not.+initialize :: MVar ThreadId -> Arduino Bool+initialize ltid = do+     -- Step 0: Set up the listener thread+     tid <- setupListener+     liftIO $ putMVar ltid tid+     -- Step 1: Send a reset to get things going+     send SystemReset      -- Step 2: Send query-firmware, and wait until we get a response-     handshake QueryFirmware-               (\r -> case r of {Firmware{} -> True; _ -> False})-               (\(Firmware v1 v2 m) -> modify (\s -> s{firmataID = "Firmware v" ++ show v1 ++ "." ++ show v2 ++ "(" ++ m ++ ")"}))-     -- Step 3: Send a capabilities request-     handshake CapabilityQuery-               (\r -> case r of {Capabilities{} -> True; _ -> False})-               (\(Capabilities c) -> modify (\s -> s{capabilities = c}))-     -- Step 4: Send analog-mapping query-     handshake AnalogMappingQuery-               (\r -> case r of {AnalogMapping{} -> True; _ -> False})-               (\(AnalogMapping bs) -> do BoardCapabilities m <- gets capabilities-                                          modify (\s -> s{capabilities = BoardCapabilities (M.mapWithKey (mapAnalog bs) m)}))-     -- We're done, print capabilities in debug mode-     cs <- gets capabilities-     dbg <- gets message-     liftIO $ dbg $ "Handshake complete. Board capabilities:\n" ++ show cs- where handshake msg isOK process = do+     -- To accommodate for the case when standard-Firmata may not be running,+     -- we will time out after 10 seconds of waiting, which should be plenty+     mbTo <- handshake QueryFirmware (Just (5000000 :: Int))+                       (\r -> case r of {Firmware{} -> True; _ -> False})+                       (\(Firmware v1 v2 m) -> modify (\s -> s{firmataID = "Firmware v" ++ show v1 ++ "." ++ show v2 ++ "(" ++ m ++ ")"}))+     case mbTo of+       Nothing -> return False  -- timed out+       Just () -> do -- Step 3: Send a capabilities request+                     _ <- handshake CapabilityQuery Nothing+                                    (\r -> case r of {Capabilities{} -> True; _ -> False})+                                    (\(Capabilities c) -> modify (\s -> s{capabilities = c}))+                     -- Step 4: Send analog-mapping query+                     _ <- handshake AnalogMappingQuery Nothing+                                    (\r -> case r of {AnalogMapping{} -> True; _ -> False})+                                    (\(AnalogMapping as) -> do BoardCapabilities m <- gets capabilities+                                                               -- need to put capabilities to both outer and inner state+                                                               let caps = BoardCapabilities (M.mapWithKey (mapAnalog as) m)+                                                               modify (\s -> s{capabilities = caps})+                                                               bs <- gets boardState+                                                               liftIO $ modifyMVar_ bs $ \bst -> return bst{boardCapabilities = caps})+                     -- We're done, print capabilities in debug mode+                     caps <- gets capabilities+                     dbg <- gets message+                     liftIO $ dbg $ "Handshake complete. Board capabilities:\n" ++ show caps+                     return True+ where handshake msg mbTOut isOK process = do            dbg <- gets message            send msg-           let wait = do resp <- recv-                         if isOK resp-                            then process resp-                            else do liftIO $ dbg $ "Skpping unexpected response: " ++ show resp-                                    wait+           let wait = do mbResp <- case mbTOut of+                                     Nothing -> Just `fmap` recv+                                     Just n  -> recvTimeOut n+                         case mbResp of+                           Nothing   -> return Nothing+                           Just resp -> if isOK resp+                                        then Just `fmap` process resp+                                        else do liftIO $ dbg $ "Skipping unexpected response: " ++ show resp+                                                wait            wait        mapAnalog bs p c           | i < rl && m /= 0x7f
System/Hardware/Arduino/Data.hs view
@@ -12,10 +12,11 @@ {-# LANGUAGE DeriveFunctor               #-} {-# LANGUAGE GeneralizedNewtypeDeriving  #-} {-# LANGUAGE NamedFieldPuns              #-}+{-# LANGUAGE RankNTypes                  #-} module System.Hardware.Arduino.Data where  import Control.Applicative        (Applicative)-import Control.Concurrent         (Chan, MVar, modifyMVar, modifyMVar_, readMVar)+import Control.Concurrent         (Chan, MVar, modifyMVar, modifyMVar_, withMVar, ThreadId) import Control.Monad.State        (StateT, MonadIO, MonadState, gets, liftIO) import Data.Bits                  ((.&.), (.|.), setBit) import Data.List                  (intercalate)@@ -71,13 +72,15 @@              deriving (Eq, Show, Enum)  -- | A request, as sent to Arduino-data Request = QueryFirmware                        -- ^ Query the Firmata version installed+data Request = SystemReset                          -- ^ Send system reset+             | QueryFirmware                        -- ^ Query the Firmata version installed              | CapabilityQuery                      -- ^ Query the capabilities of the board              | AnalogMappingQuery                   -- ^ Query the mapping of analog pins              | SetPinMode         Pin  PinMode      -- ^ Set the mode on a pin              | DigitalReport      Port Bool         -- ^ Digital report values on port enable/disable              | AnalogReport       Pin  Bool         -- ^ Analog report values on pin enable/disable              | DigitalPortWrite   Port Word8 Word8  -- ^ Set the values on a port digitally+             | SamplingInterval   Word8 Word8       -- ^ Set the sampling interval              deriving Show  -- | A response, as returned from the Arduino@@ -85,6 +88,7 @@               | Capabilities BoardCapabilities       -- ^ Capabilities report               | AnalogMapping [Word8]                -- ^ Analog pin mappings               | DigitalMessage Port Word8 Word8      -- ^ Status of a port+              | AnalogMessage  Pin  Word8 Word8      -- ^ Status of an analog pin               | Unimplemented (Maybe String) [Word8] -- ^ Represents messages currently unsupported  instance Show Response where@@ -92,6 +96,7 @@   show (Capabilities b)        = "Capabilities:\n" ++ show b   show (AnalogMapping bs)      = "AnalogMapping: " ++ showByteList bs   show (DigitalMessage p l h)  = "DigitalMessage " ++ show p ++ " = " ++ showByte l ++ " " ++ showByte h+  show (AnalogMessage  p l h)  = "AnalogMessage "  ++ show p ++ " = " ++ showByte l ++ " " ++ showByte h   show (Unimplemented mbc bs)  = "Unimplemeneted " ++ fromMaybe "" mbc ++ " " ++ showByteList bs  -- | Resolution, as referred to in http://firmata.org/wiki/Protocol#Capability_Query@@ -118,22 +123,54 @@                }                deriving Show +-- | LCD's connected to the board+newtype LCD = LCD Int+            deriving (Eq, Ord, Show)++-- | Hitachi LCD controller: See: <http://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller>.+-- We model only the 4-bit variant, with RS and EN lines only. (The most common Arduino usage.)+-- The data sheet can be seen at: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>.+data LCDController = Hitachi44780 {+                       lcdRS       :: Pin  -- ^ Hitachi pin @ 4@: Register-select+                     , lcdEN       :: Pin  -- ^ Hitachi pin @ 6@: Enable+                     , lcdD4       :: Pin  -- ^ Hitachi pin @11@: Data line @4@+                     , lcdD5       :: Pin  -- ^ Hitachi pin @12@: Data line @5@+                     , lcdD6       :: Pin  -- ^ Hitachi pin @13@: Data line @6@+                     , lcdD7       :: Pin  -- ^ Hitachi pin @14@: Data line @7@+                     , lcdRows     :: Int  -- ^ Number of rows (typically 1 or 2, upto 4)+                     , lcdCols     :: Int  -- ^ Number of cols (typically 16 or 20, upto 40)+                     , dotMode5x10 :: Bool -- ^ Set to True if 5x10 dots are used+                     }+                     deriving Show++-- | State of the LCD, a mere 8-bit word for the Hitachi+data LCDData = LCDData {+                  lcdDisplayMode    :: Word8         -- ^ Display mode (left/right/scrolling etc.)+                , lcdDisplayControl :: Word8         -- ^ Display control (blink on/off, display on/off etc.)+                , lcdGlyphCount     :: Word8         -- ^ Count of custom created glyphs (typically at most 8)+                , lcdController     :: LCDController -- ^ Actual controller+                }+ -- | State of the board data BoardState = BoardState {-                    analogReportingPins  :: S.Set Pin         -- ^ Which analog pins are reporting-                  , digitalReportingPins :: S.Set Pin         -- ^ Which digital pins are reporting-                  , pinStates            :: M.Map Pin PinData -- ^ For-each pin, store its data-                  , digitalWakeUpQueue   :: [MVar ()]         -- ^ Semaphore list to wake-up upon receiving a digital message for this pin+                    boardCapabilities    :: BoardCapabilities  -- ^ Capabilities of the board+                  , analogReportingPins  :: S.Set Pin          -- ^ Which analog pins are reporting+                  , digitalReportingPins :: S.Set Pin          -- ^ Which digital pins are reporting+                  , pinStates            :: M.Map Pin PinData  -- ^ For-each pin, store its data+                  , digitalWakeUpQueue   :: [MVar ()]          -- ^ Semaphore list to wake-up upon receiving a digital message+                  , lcds                 :: M.Map LCD LCDData  -- ^ LCD's attached to the board                   }  -- | State of the computation data ArduinoState = ArduinoState {-                message       :: String -> IO ()     -- ^ Current debugging routine-              , port          :: SerialPort          -- ^ Serial port we are communicating on-              , firmataID     :: String              -- ^ The ID of the board (as identified by the Board itself)-              , capabilities  :: BoardCapabilities   -- ^ Capabilities of the board-              , boardState    :: MVar BoardState     -- ^ Current state of the board-              , deviceChannel :: Chan Response       -- ^ Incoming messages from the board+                message       :: String -> IO ()                      -- ^ Current debugging routine+              , bailOut       :: forall a. String -> [String] -> IO a -- ^ Clean-up and quit with a hopefully informative message+              , port          :: SerialPort                           -- ^ Serial port we are communicating on+              , firmataID     :: String                               -- ^ The ID of the board (as identified by the Board itself)+              , boardState    :: MVar BoardState                      -- ^ Current state of the board+              , deviceChannel :: Chan Response                        -- ^ Incoming messages from the board+              , capabilities  :: BoardCapabilities                    -- ^ Capabilities of the board+              , listenerTid   :: MVar ThreadId                        -- ^ ThreadId of the listener               }  -- | The Arduino monad.@@ -145,6 +182,11 @@ debug s = do f <- gets message              liftIO $ f s +-- | Bailing out: print the given string on stdout and die+die :: String -> [String] -> Arduino a+die m ms = do f <- gets bailOut+              liftIO $ f m ms+ -- | Which modes does this pin support? getPinModes :: Pin -> Arduino [PinMode] getPinModes p = do@@ -157,11 +199,12 @@ getPinData :: Pin -> Arduino PinData getPinData p = do   bs  <- gets boardState-  bst <- liftIO $ readMVar bs-  case p `M.lookup` pinStates bst of-    Nothing -> die ("Trying to access " ++ show p ++ " without proper configuration.")-                   ["Make sure that you use 'setPinMode' to configure this pin first."]-    Just pd -> return pd+  err <- gets bailOut+  liftIO $ withMVar bs $ \bst ->+     case p `M.lookup` pinStates bst of+       Nothing -> err ("Trying to access " ++ show p ++ " without proper configuration.")+                      ["Make sure that you use 'setPinMode' to configure this pin first."]+       Just pd -> return pd  -- | Given a pin, collect the digital value corresponding to the -- port it belongs to, where the new value of the current pin is given@@ -185,11 +228,7 @@          [b0, b1, b2, b3, b4, b5, b6, b7] = map getVal [0 .. 7]          lsb = foldr (\(i, b) m -> if b then m `setBit` i     else m) 0 (zip [0..] [b0, b1, b2, b3, b4, b5, b6])          msb = foldr (\(i, b) m -> if b then m `setBit` (i-7) else m) 0 (zip [7..] [b7])-     -- update internal-value of the pin-     let bst' = bst {pinStates = M.insertWith (\_ o -> o{pinValue = Just (Left newValue)})-                                              curPin-                                              PinData{pinMode = OUTPUT, pinValue = Just (Left newValue)}-                                              (pinStates bst)}+         bst' = bst{pinStates = M.insert curPin PinData{pinMode = OUTPUT, pinValue = Just (Left newValue)}(pinStates bst)}      return (bst', (lsb, msb))  -- | Keep track of listeners on a digital message@@ -316,11 +355,42 @@             -> die ("Invalid mode " ++ show m ++ " set for " ++ show p)                    ["Supported modes for this pin are: " ++ unwords (if null ms then ["NONE"] else map show ms)]           _ -> return ()-        -- register the pin mode+        -- see if there was a mode already set for this pin+        bs  <- gets boardState+        mbOldMode <- liftIO $ withMVar bs $ \bst ->+                                case p `M.lookup` pinStates bst of+                                  Nothing -> return Nothing -- completely new, register+                                  Just pd -> return $ Just $ pinMode pd+        -- depending on old/new mode, determine what actions to take+        let registerNewMode = modifyMVar_ bs $ \bst -> return bst{pinStates = M.insert p PinData{pinMode = m, pinValue = Nothing} (pinStates bst) }+        case mbOldMode of+          Nothing -> do liftIO registerNewMode+                        getModeActions p m+          Just m' | m == m' -> return []  -- no mode change, nothing to do+                  | True    -> do liftIO registerNewMode+                                  remActs <- getRemovalActions p m'+                                  addActs <- getModeActions p m+                                  return $ remActs ++ addActs++-- | A mode was removed from this pin, update internal state and determine any necessary actions to remove it+getRemovalActions :: Pin -> PinMode -> Arduino [Request]+getRemovalActions p INPUT  = do -- This pin is no longer digital input         bs <- gets boardState-        liftIO $ modifyMVar_ bs $ \bst -> return bst{pinStates = M.insert p PinData{pinMode = m, pinValue = Nothing} (pinStates bst) }-        -- now return extra actions we need to take for this mode-        getModeActions p m+        liftIO $ modifyMVar bs $ \bst -> do+                let dPins = p `S.delete` digitalReportingPins bst+                    port  = pinPort p+                    acts  = [DigitalReport port False | port `notElem` map pinPort (S.elems dPins)]   -- no need for a digital report on this port anymore+                    bst'  = bst { digitalReportingPins = dPins }+                return (bst', acts)+getRemovalActions p ANALOG = do -- This pin is no longer analog+        bs <- gets boardState+        liftIO $ modifyMVar bs $ \bst -> do+                let aPins = analogReportingPins bst+                    acts  = [AnalogReport p False | p `S.member` aPins] -- no need for an analog report on this port anymore+                    bst'  = bst { analogReportingPins = p `S.delete` aPins }+                return (bst', acts)+getRemovalActions _ OUTPUT = return []+getRemovalActions p m = die ("hArduino: getRemovalActions: TBD: Unsupported mode: " ++ show m) ["On pin " ++ show p]  -- | Depending on a mode-set call, determine what further -- actions should be executed, such as enabling/disabling pin/port reporting@@ -349,4 +419,5 @@                                    , digitalReportingPins = dPins                                    }                     return (bst', acts1 ++ acts2)-getModeActions _ _    = return []+getModeActions _ OUTPUT = return []+getModeActions p m      = die ("hArduino: getModeActions: TBD: Unsupported mode: " ++ show m) ["On pin " ++ show p]
− System/Hardware/Arduino/Examples/Blink.hs
@@ -1,36 +0,0 @@----------------------------------------------------------------------------------- |--- Module      :  System.Hardware.Arduino.Examples.Blink--- Copyright   :  (c) Levent Erkok--- License     :  BSD3--- Maintainer  :  erkokl@gmail.com--- Stability   :  experimental------ The /hello world/ of the arduino world, blinking the led.----------------------------------------------------------------------------------module System.Hardware.Arduino.Examples.Blink where--import Control.Monad           (forever)-import Control.Monad.Trans     (liftIO)-import System.IO               (hSetBuffering, BufferMode(NoBuffering), stdout)--import System.Hardware.Arduino---- | Blink the led connected to port 13 on the Arduino UNO board.--- The blinking will synchronize with the printing of a dot on stdout.------ Depending on your set-up, you will need to change the path to the--- USB board. If you have problems, try changing the first argument--- to 'True' in the call to 'withArduino', which will hopefully print--- a useful diagnostic message.-blink :: IO ()-blink = withArduino False "/dev/cu.usbmodemfd131" $ do-           liftIO $ hSetBuffering stdout NoBuffering-           let led = pin 13-           setPinMode led OUTPUT-           forever $ do liftIO $ putStr "."-                        digitalWrite led True-                        delay 1000-                        digitalWrite led False-                        delay 1000
− System/Hardware/Arduino/Examples/Switch.hs
@@ -1,49 +0,0 @@----------------------------------------------------------------------------------- |--- Module      :  System.Hardware.Arduino.Examples.Switch--- Copyright   :  (c) Levent Erkok--- License     :  BSD3--- Maintainer  :  erkokl@gmail.com--- Stability   :  experimental------ Reads the value of a push-button switch and displays it continuously----------------------------------------------------------------------------------module System.Hardware.Arduino.Examples.Switch where--import Control.Monad.Trans (liftIO)-import System.IO           (hSetBuffering, BufferMode(NoBuffering), stdout)--import System.Hardware.Arduino---- | Read the value of a push-button switch (NO - normally open)--- connected to input pin 2 on the Arduino. We will continuously--- monitor and print the value as it changes. Also, we'll turn--- the led on pin 7 on when the switch is pressed.------ The wiring diagram is fairly straightforward:------     Switch: ~10K pull-down resistor, between pin 2 and GND---             Push-button NO-switch (normally open) between pin-2 and 5V------     Led   : ~10K pull-down resistor between pin-7 and led+---             Led between GND and the resistor------ Don't neglect the resistors to make sure you don't do a short-circuit!-switch :: IO ()-switch = withArduino False "/dev/cu.usbmodemfd131" $ do-            liftIO $ hSetBuffering stdout NoBuffering-            setPinMode led OUTPUT-            setPinMode button INPUT-            current <- digitalRead button-            report (not current) current-            go current- where button = pin 2-       led    = pin 7-       report prev new-         | prev /= new = do liftIO $ putStrLn $ "Button is currently " ++ if new then "ON" else "OFF"-                            digitalWrite led new-         | True        = return ()-       go prev = do new <- waitFor button-                    report prev new-                    go new
System/Hardware/Arduino/Firmata.hs view
@@ -6,11 +6,165 @@ -- Maintainer  :  erkokl@gmail.com -- Stability   :  experimental ----- The Firmata protocol, as implemented in Haskell.--- See: www.firmata.org+-- Implementation of the firmata protocol --------------------------------------------------------------------------------module System.Hardware.Arduino.Firmata-  (module System.Hardware.Arduino.Firmata.Basics)-  where -import System.Hardware.Arduino.Firmata.Basics+{-# LANGUAGE NamedFieldPuns #-}+module System.Hardware.Arduino.Firmata where++import Control.Concurrent  (newEmptyMVar, readMVar)+import Control.Monad       (when, unless, void)+import Control.Monad.Trans (liftIO)+import Data.Bits           ((.&.), shiftR)+import Data.Word           (Word8)++import System.Hardware.Arduino.Data+import System.Hardware.Arduino.Comm+import qualified System.Hardware.Arduino.Utils as U++-- | Retrieve the Firmata firmware version running on the Arduino. The first+-- component is the major, second is the minor. The final value is a human+-- readable identifier for the particular board.+queryFirmware :: Arduino (Word8, Word8, String)+queryFirmware = do+        send QueryFirmware+        r <- recv+        case r of+          Firmware v1 v2 m -> return (v1, v2, m)+          _                -> die "queryFirmware: Got unexpected response for query firmware call: " [show r]++-- | Delay the computaton for a given number of milli-seconds+delay :: Int -> Arduino ()+delay = liftIO . U.delay++-- | Set the mode on a particular pin on the board+setPinMode :: Pin -> PinMode -> Arduino ()+setPinMode p m = do+   extras <- registerPinMode p m+   send $ SetPinMode p m+   mapM_ send extras++-- | Set or clear a digital pin on the board+digitalWrite :: Pin -> Bool -> Arduino ()+digitalWrite p v = do+   -- first make sure we have this pin set as output+   pd <- getPinData p+   when (pinMode pd /= OUTPUT) $ die ("Invalid digitalWrite call on pin " ++ show p)+                                       [ "The current mode for this pin is: " ++ show (pinMode pd)+                                       , "For digitalWrite, it must be set to: " ++ show OUTPUT+                                       , "via a proper call to setPinMode"+                                       ]+   case pinValue pd of+     Just (Left b) | b == v -> return () -- no change, nothing to do+     _                      -> do (lsb, msb) <- computePortData p v+                                  send $ DigitalPortWrite (pinPort p) lsb msb++-- | Turn on/off internal pull-up resistor on an input pin+pullUpResistor :: Pin -> Bool -> Arduino ()+pullUpResistor p v = do+   -- first make sure we have this pin set as input+   pd <- getPinData p+   when (pinMode pd /= INPUT) $ die ("Invalid turnOnPullUpResistor call on pin " ++ show p)+                                      [ "The current mode for this pin is: " ++ show (pinMode pd)+                                      , "For turnOnPullUpResistor, it must be set to: " ++ show INPUT+                                      , "via a proper call to setPinMode"+                                      ]+   (lsb, msb) <- computePortData p v+   send $ DigitalPortWrite (pinPort p) lsb msb++-- | Read the value of a pin in digital mode; this is a non-blocking call, returning+-- the current value immediately. See 'waitFor' for a version that waits for a change+-- in the pin first.+digitalRead :: Pin -> Arduino Bool+digitalRead p = do+   -- first make sure we have this pin set as input+   pd <- getPinData p+   when (pinMode pd /= INPUT) $ die ("Invalid digitalRead call on pin " ++ show p)+                                      [ "The current mode for this pin is: " ++ show (pinMode pd)+                                      , "For digitalWrite, it must be set to: " ++ show INPUT+                                      , "via a proper call to setPinMode"+                                      ]+   return $ case pinValue pd of+              Just (Left v) -> v+              _             -> False -- no (correctly-typed) value reported yet, default to False++-- | Wait for a change in the value of the digital input pin. Returns the new value.+-- Note that this is a blocking call. For a non-blocking version, see 'digitalRead', which returns the current+-- value of a pin immediately.+waitFor :: Pin -> Arduino Bool+waitFor p = head `fmap` waitAny [p]++-- | Wait for a change in any of the given pins. Once a change is detected, all the new values are+-- returned. Similar to 'waitFor', but is useful when we are watching multiple digital inputs.+waitAny :: [Pin] -> Arduino [Bool]+waitAny ps = map snd `fmap` waitGeneric ps++-- | Wait for any of the given pins to go from low to high. If all of the pins are high to start+-- with, then we first wait for one of them to go low, and then wait for one of them to go back high.+-- Returns the new values.+waitAnyHigh :: [Pin] -> Arduino [Bool]+waitAnyHigh ps = do+   curVals <- mapM digitalRead ps+   when (and curVals) $ void $ waitAnyLow ps   -- all are H to start with, wait for at least one to go low+   vs <- waitGeneric ps  -- wait for some change+   if (False, True) `elem` vs+      then return $ map snd vs+      else waitAnyHigh ps++-- | Wait for any of the given pins to go from high to low. If all of the pins are low to start+-- with, then we first wait for one of them to go high, and then wait for one of them to go back low.+-- Returns the new values.+waitAnyLow :: [Pin] -> Arduino [Bool]+waitAnyLow ps = do+   curVals <- mapM digitalRead ps+   unless (or curVals) $ void $ waitAnyHigh ps   -- all are L to start with, wait for at least one to go high+   vs <- waitGeneric ps  -- wait for some change+   if (True, False) `elem` vs+      then return $ map snd vs+      else waitAnyLow ps++-- | A utility function, waits for any change on any given pin+-- and returns both old and new values. It's guaranteed that+-- at least one returned pair have differing values.+waitGeneric :: [Pin] -> Arduino [(Bool, Bool)]+waitGeneric ps = do+   curVals <- mapM digitalRead ps+   semaphore <- liftIO newEmptyMVar+   let wait = do digitalWakeUp semaphore+                 liftIO $ readMVar semaphore+                 newVals <- mapM digitalRead ps+                 if curVals == newVals+                    then wait+                    else return $ zip curVals newVals+   wait++-- | Read the value of a pin in analog mode; this is a non-blocking call, immediately+-- returning the last sampled value. It returns @0@ if the voltage on the pin+-- is 0V, and @1023@ if it is 5V, properly scaled. (See `setAnalogSamplingInterval` for+-- sampling frequency.)+analogRead :: Pin -> Arduino Int+analogRead p = do+   -- first make sure we have this pin set as analog+   pd <- getPinData p+   when (pinMode pd /= ANALOG) $ die ("Invalid analogRead call on pin " ++ show p)+                                     [ "The current mode for this pin is: " ++ show (pinMode pd)+                                     , "For analogRead, it must be set to: " ++ show ANALOG+                                     , "via a proper call to setPinMode"+                                     ]+   return $ case pinValue pd of+              Just (Right v) -> v+              _              -> 0 -- no (correctly-typed) value reported yet, default to False++-- | Set the analog sampling interval, in milliseconds. Arduino uses a default of 19ms to sample analog and I2C+-- signals, which is fine for many applications, but can be modified if needed. The argument+-- should be a number between @10@ and @16384@; @10@ being the minumum sampling interval supported by Arduino+-- and @16383@ being the largest value we can represent in 14 bits that this message can handle. (Note that+-- the largest value is just about @16@ seconds, which is plenty infrequent for all practical needs.)+setAnalogSamplingInterval :: Int -> Arduino ()+setAnalogSamplingInterval i+  | i < 10 || i > 16383+  = die ("hArduino: setAnalogSamplingInterval: Allowed interval is [10, 16383] ms, received: " ++ show i) []+  | True+  = send $ SamplingInterval (fromIntegral lsb) (fromIntegral msb)+  where lsb = i .&. 0x7f+        msb = (i `shiftR` 7) .&. 0x7f
− System/Hardware/Arduino/Firmata/Basics.hs
@@ -1,93 +0,0 @@----------------------------------------------------------------------------------- |--- Module      :  System.Hardware.Arduino.Firmata.Basics--- Copyright   :  (c) Levent Erkok--- License     :  BSD3--- Maintainer  :  erkokl@gmail.com--- Stability   :  experimental------ Basic components of the firmata protocol----------------------------------------------------------------------------------{-# LANGUAGE NamedFieldPuns #-}-module System.Hardware.Arduino.Firmata.Basics where--import Control.Concurrent  (newEmptyMVar, readMVar)-import Control.Monad       (when)-import Control.Monad.Trans (liftIO)-import Data.Word           (Word8)--import System.Hardware.Arduino.Data-import System.Hardware.Arduino.Comm-import qualified System.Hardware.Arduino.Utils as U---- | Retrieve the Firmata firmware version running on the Arduino. The first--- component is the major, second is the minor. The final value is a human--- readable identifier for the particular board.-queryFirmware :: Arduino (Word8, Word8, String)-queryFirmware = do-        send QueryFirmware-        r <- recv-        case r of-          Firmware v1 v2 m -> return (v1, v2, m)-          _                -> error $ "queryFirmware: Got unexpected response for query firmware call: " ++ show r---- | Delay the computaton for a given number of milli-seconds-delay :: Int -> Arduino ()-delay = liftIO . U.delay---- | Set the mode on a particular pin on the board-setPinMode :: Pin -> PinMode -> Arduino ()-setPinMode p m = do-   extras <- registerPinMode p m-   send $ SetPinMode p m-   mapM_ send extras---- | Set or clear a digital pin on the board-digitalWrite :: Pin -> Bool -> Arduino ()-digitalWrite p v = do-   -- first make sure we have this pin set as output-   pd <- getPinData p-   when (pinMode pd /= OUTPUT) $ U.die ("Invalid digitalWrite call on pin " ++ show p)-                                       [ "The current mode for this pin is: " ++ show (pinMode pd)-                                       , "For digitalWrite, it must be set to: " ++ show OUTPUT-                                       , "via a proper call to setPinMode"-                                       ]-   (lsb, msb) <- computePortData p v-   send $ DigitalPortWrite (pinPort p) lsb msb---- | Read the value of a pin in digital mode; this is a non-blocking call, returning--- the current value immediately. See 'waitFor' for a version that waits for a change--- in the pin first.-digitalRead :: Pin -> Arduino Bool-digitalRead p = do-   -- first make sure we have this pin set as input-   pd <- getPinData p-   when (pinMode pd /= INPUT) $ U.die ("Invalid digitalRead call on pin " ++ show p)-                                      [ "The current mode for this pin is: " ++ show (pinMode pd)-                                      , "For digitalWrite, it must be set to: " ++ show INPUT-                                      , "via a proper call to setPinMode"-                                      ]-   return $ case pinValue pd of-              Just (Left v) -> v-              _             -> False -- no (correctly-typed) value reported yet, default to False---- | Wait for a change in the value of the digital input pin. Returns the new value.--- Note that this is a blocking call. For a non-blocking version, see 'digitalRead', which returns the current--- value of a pin immediately.-waitFor :: Pin -> Arduino Bool-waitFor p = do-   curVal <- digitalRead p-   let wait = do sleepTillDigitalMessage-                 newVal <- digitalRead p-                 if newVal == curVal-                    then wait-                    else return newVal-   wait---- | Sleep until we receive a digital message from the board-sleepTillDigitalMessage :: Arduino ()-sleepTillDigitalMessage = do-        semaphore <- liftIO newEmptyMVar-        digitalWakeUp semaphore-        liftIO $ readMVar semaphore
+ System/Hardware/Arduino/LCD.hs view
@@ -0,0 +1,432 @@+-------------------------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.LCD+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- LCD (Liquid Crystal Display) parts supported by hArduino. The Haskell code+-- below has partly been implemented following the Arduino LiquidCrystal project+-- source code: <http://code.google.com/p/arduino/source/browse/trunk/libraries/LiquidCrystal/>+--+-- The Hitachi44780 data sheet is at: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>+--+-- For an example program using this library, see "System.Hardware.Arduino.SamplePrograms.LCD".+-------------------------------------------------------------------------------------------------++{-# LANGUAGE NamedFieldPuns #-}+module System.Hardware.Arduino.LCD(+  -- * LCD types and registration+  LCDController(..), lcdRegister+  -- * Writing text on the LCD+  , lcdClear, lcdWrite+  -- * Moving the cursor+  , lcdHome, lcdSetCursor+  -- * Scrolling+  , lcdAutoScrollOn, lcdAutoScrollOff+  , lcdScrollDisplayLeft, lcdScrollDisplayRight+  -- * Display properties+  , lcdLeftToRight, lcdRightToLeft+  , lcdBlinkOn, lcdBlinkOff+  , lcdCursorOn, lcdCursorOff+  , lcdDisplayOn, lcdDisplayOff+  -- * Accessing internal symbols,+  , LCDSymbol, lcdInternalSymbol, lcdWriteSymbol+  -- Creating custom symbols+  , lcdCreateSymbol+  -- * Misc helpers+  , lcdFlash+  )  where++import Control.Concurrent  (modifyMVar, withMVar)+import Control.Monad       (when)+import Control.Monad.State (gets, liftIO)+import Data.Bits           (testBit, (.|.), (.&.), setBit, clearBit, shiftL, bit)+import Data.Char           (ord, isSpace)+import Data.Maybe          (fromMaybe)+import Data.Word           (Word8)++import qualified Data.Map as M++import System.Hardware.Arduino.Data+import System.Hardware.Arduino.Firmata++import qualified System.Hardware.Arduino.Utils as U++---------------------------------------------------------------------------------------+-- Low level interface, not available to the user+---------------------------------------------------------------------------------------++-- | Commands understood by Hitachi+data Cmd = LCD_INITIALIZE+         | LCD_INITIALIZE_END+         | LCD_FUNCTIONSET+         | LCD_DISPLAYCONTROL Word8+         | LCD_CLEARDISPLAY+         | LCD_ENTRYMODESET   Word8+         | LCD_RETURNHOME+         | LCD_SETDDRAMADDR   Word8+         | LCD_CURSORSHIFT    Word8+         | LCD_SETCGRAMADDR   Word8++-- | Convert a command to a data-word+getCmdVal :: LCDController -> Cmd -> Word8+getCmdVal Hitachi44780{lcdRows, dotMode5x10} = get+  where multiLine -- bit 3+          | lcdRows > 1 = 0x08 :: Word8+          | True        = 0x00 :: Word8+        dotMode   -- bit 2+          | dotMode5x10 = 0x04 :: Word8+          | True        = 0x00 :: Word8+        displayFunction = multiLine .|. dotMode+        get LCD_INITIALIZE         = 0x33+        get LCD_INITIALIZE_END     = 0x32+        get LCD_FUNCTIONSET        = 0x20 .|. displayFunction+        get (LCD_DISPLAYCONTROL w) = 0x08 .|. w+        get LCD_CLEARDISPLAY       = 0x01+        get (LCD_ENTRYMODESET w)   = 0x04 .|. w+        get LCD_RETURNHOME         = 0x02+        get (LCD_SETDDRAMADDR w)   = 0x80 .|. w+        get (LCD_CURSORSHIFT w)    = 0x10 .|. 0x08 .|. w   -- NB. LCD_DISPLAYMOVE (0x08) hard coded here+        get (LCD_SETCGRAMADDR w)   = 0x40 .|. w `shiftL` 3++-- | Initialize the LCD. Follows the data sheet <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>,+-- page 46; figure 24.+initLCD :: LCD -> LCDController -> Arduino ()+initLCD lcd c@Hitachi44780{lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7} = do+    debug "Starting the LCD initialization sequence"+    mapM_ (`setPinMode` OUTPUT) [lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7]+    -- Wait for 50ms, data-sheet says at least 40ms for 2.7V version, so be safe+    delay 50+    sendCmd c LCD_INITIALIZE+    delay 5+    sendCmd c LCD_INITIALIZE_END+    sendCmd c LCD_FUNCTIONSET+    lcdCursorOff lcd+    lcdBlinkOff lcd+    lcdLeftToRight lcd+    lcdAutoScrollOff lcd+    lcdHome lcd+    lcdClear lcd+    lcdDisplayOn lcd++-- | Get the controller associated with the LCD+getController :: LCD -> Arduino LCDController+getController lcd = do+  bs  <- gets boardState+  err <- gets bailOut+  liftIO $ withMVar bs $ \bst -> case lcd `M.lookup` lcds bst of+                                   Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []+                                   Just ld -> return $ lcdController ld++-- | Send a command to the LCD controller+sendCmd :: LCDController -> Cmd -> Arduino ()+sendCmd c = transmit False c . getCmdVal c++-- | Send 4-bit data to the LCD controller+sendData :: LCDController -> Word8 -> Arduino ()+sendData lcd n = do debug $ "Transmitting LCD data: " ++ U.showByte n+                    transmit True lcd n++-- | By controlling the enable-pin, indicate to the controller that+-- the data is ready for it to process.+pulseEnable :: LCDController -> Arduino ()+pulseEnable Hitachi44780{lcdEN} = do+  debug "Sending LCD pulseEnable"+  digitalWrite lcdEN False+  delay 1+  digitalWrite lcdEN True+  delay 1+  digitalWrite lcdEN False+  delay 1++-- | Transmit data down to the LCD+transmit :: Bool -> LCDController -> Word8 -> Arduino ()+transmit mode c@Hitachi44780{lcdRS, lcdEN, lcdD4, lcdD5, lcdD6, lcdD7} val = do+  digitalWrite lcdRS mode+  digitalWrite lcdEN False+  let [b7, b6, b5, b4, b3, b2, b1, b0] = [val `testBit` i | i <- [7, 6 .. 0]]+  -- Send down the first 4 bits+  digitalWrite lcdD4 b4+  digitalWrite lcdD5 b5+  digitalWrite lcdD6 b6+  digitalWrite lcdD7 b7+  pulseEnable c+  -- Send down the remaining batch+  digitalWrite lcdD4 b0+  digitalWrite lcdD5 b1+  digitalWrite lcdD6 b2+  digitalWrite lcdD7 b3+  pulseEnable c++-- | Helper function to simplify library programming, not exposed to the user.+withLCD :: LCD -> String -> (LCDController -> Arduino a) -> Arduino a+withLCD lcd what action = do+        debug what+        c <- getController lcd+        action c++---------------------------------------------------------------------------------------+-- High level interface, exposed to the user+---------------------------------------------------------------------------------------++-- | Register an LCD controller. When registration is complete, the LCD will be initialized so that:+--+--   * Set display ON (Use 'lcdDisplayOn' / 'lcdDisplayOff' to change.)+--+--   * Set cursor OFF (Use 'lcdCursorOn' / 'lcdCursorOff' to change.)+--+--   * Set blink OFF  (Use 'lcdBlinkOn' / 'lcdBlinkOff' to change.)+--+--   * Clear display (Use 'lcdClear' to clear, 'lcdWrite' to display text.)+--+--   * Set entry mode left to write (Use 'lcdLeftToRight' / 'lcdRightToLeft' to control.)+--+--   * Set autoscrolling OFF (Use 'lcdAutoScrollOff' / 'lcdAutoScrollOn' to control.)+--+--   * Put the cursor into home position (Use 'lcdSetCursor' or 'lcdHome' to move around.)+lcdRegister :: LCDController -> Arduino LCD+lcdRegister controller = do+  bs <- gets boardState+  lcd <- liftIO $ modifyMVar bs $ \bst -> do+                    let n = M.size $ lcds bst+                        ld = LCDData { lcdDisplayMode    = 0+                                     , lcdDisplayControl = 0+                                     , lcdGlyphCount     = 0+                                     , lcdController     = controller+                                     }+                    return (bst {lcds = M.insert (LCD n) ld (lcds bst)}, LCD n)+  case controller of+     Hitachi44780{} -> initLCD lcd controller+  return lcd++-- | Write a string on the LCD at the current cursor position+lcdWrite :: LCD -> String -> Arduino ()+lcdWrite lcd m = withLCD lcd ("Writing " ++ show m ++ " to LCD") $ \c -> mapM_ (sendData c) m'+   where m' = map (\ch -> fromIntegral (ord ch) .&. 0xFF) m++-- | Clear the LCD+lcdClear :: LCD -> Arduino ()+lcdClear lcd = withLCD lcd "Sending clearLCD" $ \c ->+                 do sendCmd c LCD_CLEARDISPLAY+                    delay 2 -- give some time to make sure LCD is really cleared++-- | Send the cursor to home position+lcdHome :: LCD -> Arduino ()+lcdHome lcd = withLCD lcd "Sending the cursor home" $ \c ->+                do sendCmd c LCD_RETURNHOME+                   delay 2++-- | Set the cursor location. The pair of arguments is the new column and row numbers+-- respectively:+--+--   * The first value is the column, the second is the row. (This is counter-intuitive, but+--     is in line with what the standard Arduino programmers do, so we follow the same convention.)+--+--   * Counting starts at 0 (both for column and row no)+--+--   * If the new location is out-of-bounds of your LCD, we will put it the cursor to the closest+--     possible location on the LCD.+lcdSetCursor :: LCD -> (Int, Int) -> Arduino ()+lcdSetCursor lcd (givenCol, givenRow) = withLCD lcd ("Sending the cursor to Row: " ++ show givenRow ++ " Col: " ++ show givenCol) set+  where set c@Hitachi44780{lcdRows, lcdCols} = sendCmd c (LCD_SETDDRAMADDR offset)+              where align :: Int -> Int -> Word8+                    align i m+                      | i < 0  = 0+                      | i >= m = fromIntegral $ m-1+                      | True   = fromIntegral i+                    col = align givenCol lcdCols+                    row = align givenRow lcdRows+                    -- The magic row-offsets come from various web sources+                    -- I don't follow the logic in these numbers, but it seems to work+                    rowOffsets = [(0, 0), (1, 0x40), (2, 0x14), (3, 0x54)]+                    offset = col + fromMaybe 0x54 (row `lookup` rowOffsets)++-- | Scroll the display to the left by 1 character. Project idea: Using a tilt sensor, scroll the contents of the display+-- left/right depending on the tilt. +lcdScrollDisplayLeft :: LCD -> Arduino ()+lcdScrollDisplayLeft lcd = withLCD lcd "Scrolling display to the left by 1" $ \c -> sendCmd c (LCD_CURSORSHIFT lcdMoveLeft)+  where lcdMoveLeft = 0x00++-- | Scroll the display to the right by 1 character+lcdScrollDisplayRight :: LCD -> Arduino ()+lcdScrollDisplayRight lcd = withLCD lcd "Scrolling display to the right by 1" $ \c -> sendCmd c (LCD_CURSORSHIFT lcdMoveRight)+  where lcdMoveRight = 0x04++-- | Display characteristics helper, set the new control/mode and send+-- appropriate commands if anything changed+updateDisplayData :: String -> (Word8 -> Word8, Word8 -> Word8) -> LCD -> Arduino ()+updateDisplayData what (f, g) lcd = do+   debug what+   bs  <- gets boardState+   err <- gets bailOut+   (  LCDData {lcdDisplayControl = oldC, lcdDisplayMode = oldM}+    , LCDData {lcdDisplayControl = newC, lcdDisplayMode = newM, lcdController = c})+        <- liftIO $ modifyMVar bs $ \bst ->+                       case lcd `M.lookup` lcds bst of+                         Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []+                         Just ld@LCDData{lcdDisplayControl, lcdDisplayMode}+                            -> do let ld' = ld { lcdDisplayControl = f lcdDisplayControl+                                               , lcdDisplayMode    = g lcdDisplayMode+                                               }+                                  return (bst{lcds = M.insert lcd ld' (lcds bst)}, (ld, ld'))+   when (oldC /= newC) $ sendCmd c (LCD_DISPLAYCONTROL newC)+   when (oldM /= newM) $ sendCmd c (LCD_ENTRYMODESET   newM)++-- | Update the display control word+updateDisplayControl :: String -> (Word8 -> Word8) -> LCD -> Arduino ()+updateDisplayControl what f = updateDisplayData what (f, id)++-- | Update the display mode word+updateDisplayMode :: String -> (Word8 -> Word8) -> LCD -> Arduino ()+updateDisplayMode what g = updateDisplayData what (id, g)++-- | Various control masks for the Hitachi44780+data Hitachi44780Mask = LCD_BLINKON              -- ^ bit @0@ Controls whether cursor blinks+                      | LCD_CURSORON             -- ^ bit @1@ Controls whether cursor is on+                      | LCD_DISPLAYON            -- ^ bit @2@ Controls whether display is on+                      | LCD_ENTRYSHIFTINCREMENT  -- ^ bit @0@ Controls left/right scroll+                      | LCD_ENTRYLEFT            -- ^ bit @1@ Controls left/right entry mode++-- | Convert the mask value to the bit no+maskBit :: Hitachi44780Mask -> Int+maskBit LCD_BLINKON             = 0+maskBit LCD_CURSORON            = 1+maskBit LCD_DISPLAYON           = 2+maskBit LCD_ENTRYSHIFTINCREMENT = 0+maskBit LCD_ENTRYLEFT           = 1++-- | Clear by the mask+clearMask :: Hitachi44780Mask -> Word8 -> Word8+clearMask m w = w `clearBit` maskBit m++-- | Set by the mask+setMask :: Hitachi44780Mask -> Word8 -> Word8+setMask m w = w `setBit` maskBit m++-- | Do not blink the cursor+lcdBlinkOff :: LCD -> Arduino ()+lcdBlinkOff = updateDisplayControl "Turning blinking off" (clearMask LCD_BLINKON)++-- | Blink the cursor+lcdBlinkOn :: LCD -> Arduino ()+lcdBlinkOn = updateDisplayControl "Turning blinking on" (setMask LCD_BLINKON)++-- | Hide the cursor. Note that a blinking cursor cannot be hidden, you must first+-- turn off blinking.+lcdCursorOff :: LCD -> Arduino ()+lcdCursorOff = updateDisplayControl "Not showing the cursor" (clearMask LCD_CURSORON)++-- | Show the cursor+lcdCursorOn :: LCD -> Arduino ()+lcdCursorOn = updateDisplayControl "Showing the cursor" (setMask LCD_CURSORON)++-- | Turn the display off. Note that turning the display off does not mean you are+-- powering it down. It simply means that the characters will not be shown until+-- you turn it back on using 'lcdDisplayOn'. (Also, the contents will /not/ be+-- forgotten when you call this function.) Therefore, this function is useful+-- for temporarily hiding the display contents.+lcdDisplayOff :: LCD -> Arduino ()+lcdDisplayOff = updateDisplayControl "Turning display off" (clearMask LCD_DISPLAYON)++-- | Turn the display on+lcdDisplayOn :: LCD -> Arduino ()+lcdDisplayOn = updateDisplayControl "Turning display on" (setMask LCD_DISPLAYON)++-- | Set writing direction: Left to Right+lcdLeftToRight :: LCD -> Arduino ()+lcdLeftToRight = updateDisplayMode "Setting left-to-right entry mode" (setMask LCD_ENTRYLEFT)++-- | Set writing direction: Right to Left+lcdRightToLeft :: LCD -> Arduino ()+lcdRightToLeft = updateDisplayMode "Setting right-to-left entry mode" (clearMask LCD_ENTRYLEFT)++-- | Turn on auto-scrolling. In the context of the Hitachi44780 controller, this means that+-- each time a letter is added, all the text is moved one space to the left. This can be+-- confusing at first: It does /not/ mean that your strings will continuously scroll:+-- It just means that if you write a string whose length exceeds the column-count+-- of your LCD, then you'll see the tail-end of it. (Of course, this will create a scrolling+-- effect as the string is being printed character by character.)+--+-- Having said that, it is easy to program a scrolling string program: Simply write your string+-- by calling 'lcdWrite', and then use the 'lcdScrollDisplayLeft' and 'lcdScrollDisplayRight' functions+-- with appropriate delays to simulate the scrolling.+lcdAutoScrollOn :: LCD -> Arduino ()+lcdAutoScrollOn = updateDisplayMode "Setting auto-scroll ON" (setMask LCD_ENTRYSHIFTINCREMENT)++-- | Turn off auto-scrolling. See the comments for 'lcdAutoScrollOn' for details. When turned+-- off (which is the default), you will /not/ see the characters at the end of your strings that+-- do not fit into the display.+lcdAutoScrollOff :: LCD -> Arduino ()+lcdAutoScrollOff = updateDisplayMode "Setting auto-scroll OFF" (clearMask LCD_ENTRYSHIFTINCREMENT)++-- | Flash contents of the LCD screen+lcdFlash :: LCD+         -> Int  -- ^ Flash count+         -> Int  -- ^ Delay amount (in milli-seconds)+         -> Arduino ()+lcdFlash lcd n d = sequence_ $ concat $ replicate n [lcdDisplayOff lcd, delay d, lcdDisplayOn lcd, delay d]++-- | An abstract symbol type for user created symbols+newtype LCDSymbol = LCDSymbol Word8++-- | Create a custom symbol for later display. Note that controllers+-- have limited capability for such symbols, typically storing no more+-- than 8. The behavior is undefined if you create more symbols than your+-- LCD can handle.+--+-- The input is a simple description of the glyph, as a list of precisely 8+-- strings, each of which must have 5 characters. Any space character is+-- interpreted as a empty pixel, any non-space is a full pixel, corresponding+-- to the pixel in the 5x8 characters we have on the LCD.  For instance, here's+-- a happy-face glyph you can use:+--+-- >+-- >   [ "     "+-- >   , "@   @"+-- >   , "     "+-- >   , "     "+-- >   , "@   @"+-- >   , " @@@ "+-- >   , "     "+-- >   , "     "+-- >   ]+-- >+lcdCreateSymbol :: LCD -> [String] -> Arduino LCDSymbol+lcdCreateSymbol lcd glyph+  | length glyph /= 8 || any (/= 5) (map length glyph)+  = die "hArduino: lcdCreateSymbol: Invalid glyph description: must be 8x5!" ("Received:" : glyph)+  | True+  = do bs  <- gets boardState+       err <- gets bailOut+       (i, c) <- liftIO $ modifyMVar bs $ \bst ->+                    case lcd `M.lookup` lcds bst of+                      Nothing -> err ("hArduino: Cannot locate " ++ show lcd) []+                      Just ld@LCDData{lcdGlyphCount, lcdController}+                              -> do let ld' = ld { lcdGlyphCount = lcdGlyphCount + 1 }+                                    return (bst{lcds = M.insert lcd ld' (lcds bst)}, (lcdGlyphCount, lcdController))+       sendCmd c (LCD_SETCGRAMADDR i)+       let cvt :: String -> Word8+           cvt s = foldr (.|.) 0 [bit p | (ch, p) <- zip (reverse s) [0..], not (isSpace ch)]+       mapM_ (sendData c . cvt) glyph+       return $ LCDSymbol i++-- | Display a user created symbol on the LCD. (See 'lcdCreateSymbol' for details.)+lcdWriteSymbol :: LCD -> LCDSymbol -> Arduino ()+lcdWriteSymbol lcd (LCDSymbol i) = withLCD lcd ("Writing custom symbol " ++ show i ++ " to LCD") $ \c -> sendData c i++-- | Access an internally stored symbol, one that is not available via its ASCII equivalent. See+-- the Hitachi datasheet for possible values: <http://lcd-linux.sourceforge.net/pdfdocs/hd44780.pdf>, Table 4 on page 17.+--+-- For instance, to access the symbol right-arrow:+--+--   * Locate it in the above table: Right-arrow is at the second-to-last row, 7th character from left.+--+--   * Check the upper/higher bits as specified in the table: For Right-arrow, upper bits are @0111@ and the+--     lower bits are @1110@; which gives us the code @01111110@, or @0x7E@.+--+--   * So, right-arrow can be accessed by symbol code 'lcdInternalSymbol' @0x7E@, which will give us a 'LCDSymbol' value+--   that can be passed to the 'lcdWriteSymbol' function. The code would look like this: @lcdWriteSymbol lcd (lcdInternalSymbol 0x7E)@.+lcdInternalSymbol :: Word8 -> LCDSymbol+lcdInternalSymbol = LCDSymbol
System/Hardware/Arduino/Protocol.hs view
@@ -33,6 +33,7 @@ -- | Package a request as a sequence of bytes to be sent to the board -- using the Firmata protocol. package :: Request -> B.ByteString+package SystemReset              = nonSysEx SYSTEM_RESET            [] package QueryFirmware            = sysEx    REPORT_FIRMWARE         [] package CapabilityQuery          = sysEx    CAPABILITY_QUERY        [] package AnalogMappingQuery       = sysEx    ANALOG_MAPPING_QUERY    []@@ -40,6 +41,7 @@ package (DigitalReport p b)      = nonSysEx (REPORT_DIGITAL_PORT p) [if b then 1 else 0] package (SetPinMode p m)         = nonSysEx SET_PIN_MODE            [fromIntegral (pinNo p), fromIntegral (fromEnum m)] package (DigitalPortWrite p l m) = nonSysEx (DIGITAL_MESSAGE p)     [l, m]+package (SamplingInterval l m)   = sysEx    SAMPLING_INTERVAL       [l, m]  -- | Unpackage a SysEx response unpackageSysEx :: [Word8] -> Response@@ -67,12 +69,14 @@ unpackageNonSysEx :: (Int -> IO [Word8]) -> FirmataCmd -> IO Response unpackageNonSysEx getBytes c = grab c  where unimplemented n = Unimplemented (Just (show c)) `fmap` getBytes n-       grab (ANALOG_MESSAGE      _pin)  = unimplemented 2+       grab (ANALOG_MESSAGE       p)    = getBytes 2 >>= \[l, h] -> return (AnalogMessage  p l h)        grab (DIGITAL_MESSAGE      p)    = getBytes 2 >>= \[l, h] -> return (DigitalMessage p l h)+       -- we should never see any of the following since they are "request" codes+       -- TBD: Maybe we should put them in a different data-type        grab (REPORT_ANALOG_PIN   _pin)  = unimplemented 1        grab (REPORT_DIGITAL_PORT _port) = unimplemented 1-       grab START_SYSEX                 = unimplemented 0   -- we should never see this+       grab START_SYSEX                 = unimplemented 0        grab SET_PIN_MODE                = unimplemented 2-       grab END_SYSEX                   = unimplemented 0   -- we should never see this+       grab END_SYSEX                   = unimplemented 0        grab PROTOCOL_VERSION            = unimplemented 2        grab SYSTEM_RESET                = unimplemented 0
+ System/Hardware/Arduino/SamplePrograms/Analog.hs view
@@ -0,0 +1,43 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.Analog+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Reads the value of an analog input, controlled by a 10K potentiometer.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.Analog where++import Control.Monad       (when)+import Control.Monad.Trans (liftIO)++import System.Hardware.Arduino++-- | Read the value of an analog input line. We will print the value+-- on the screen, and also blink a led on the Arduino based on the+-- value. The smaller the value, the faster the blink.+--+-- The circuit simply has a 10K potentiometer between 5V and GND, with+-- the wiper line connected to analog input 3. We also have a led between+-- pin 13 and GND.+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Analog.png>>+analogVal :: IO ()+analogVal = withArduino False "/dev/cu.usbmodemfd131" $ do+               setPinMode led OUTPUT+               setPinMode pot ANALOG+               cur <- analogRead pot+               liftIO $ print cur+               go cur+  where led = pin 13+        pot = pin 17 -- NB. Analog-3 is pin 17 on the UNO+        go cur = do digitalWrite led True+                    delay cur+                    digitalWrite led False+                    delay cur+                    new <- analogRead pot+                    when (cur /= new) $ liftIO $ print new+                    go new
+ System/Hardware/Arduino/SamplePrograms/Blink.hs view
@@ -0,0 +1,33 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.Blink+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- The /hello world/ of the arduino world, blinking the led.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.Blink where++import Control.Monad       (forever)++import System.Hardware.Arduino++-- | Blink the led connected to port 13 on the Arduino UNO board.+--+-- Note that you do not need any other components to run this example: Just hook+-- up your Arduino to the computer and make sure StandardFirmata is running on it.+-- However, you can connect a LED between Pin13 and GND if you want to blink an+-- external led as well, as depicted below:+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Blink.png>>+blink :: IO ()+blink = withArduino False "/dev/cu.usbmodemfd131" $ do+           let led = pin 13+           setPinMode led OUTPUT+           forever $ do digitalWrite led True+                        delay 1000+                        digitalWrite led False+                        delay 1000
+ System/Hardware/Arduino/SamplePrograms/Button.hs view
@@ -0,0 +1,38 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.Button+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Reads the value of a push-button and displays it's status continuously+-- on the computer screen and by lighting a led on the Arduino as long as+-- the button is pressed.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.Button where++import Control.Monad.Trans (liftIO)++import System.Hardware.Arduino++-- | Read the value of a push-button (NO - normally open)+-- connected to input pin 2 on the Arduino. We will continuously+-- monitor and print the value as it changes. Also, we'll turn+-- the led on pin 13 on when the switch is pressed.+--+-- The wiring is straightforward: Simply put a push-button between+-- digital input 2 and +5V, guarded by a 10K resistor:+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Button.png>>+button :: IO ()+button = withArduino False "/dev/cu.usbmodemfd131" $ do+            setPinMode led OUTPUT+            setPinMode pb  INPUT+            go =<< digitalRead pb+ where pb   = pin 2+       led  = pin 13+       go s = do liftIO $ putStrLn $ "Button is currently " ++ if s then "ON" else "OFF"+                 digitalWrite led s+                 go =<< waitFor pb
+ System/Hardware/Arduino/SamplePrograms/Counter.hs view
@@ -0,0 +1,46 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.Counter+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Demonstrates using two push-buttons to count up and down.+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.Counter where++import Control.Monad.Trans (liftIO)++import System.Hardware.Arduino++-- | Two push-button switches, controlling a counter value. We will increment+-- the counter if the first one ('bUp') is pressed, and decrement the value if the+-- second one ('bDown') is pressed. We also have a led connected to pin 13 (either use+-- the internal or connect an external one), that we light up when the counter value+-- is 0.+--+-- Wiring is very simple: Up-button connected to pin 4, Down-button connected+-- to pin 2, and a led on pin 13.+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/Counter.png>>+counter :: IO ()+counter = withArduino False "/dev/cu.usbmodemfd131" $ do+            setPinMode led   OUTPUT+            setPinMode bUp   INPUT+            setPinMode bDown INPUT+            update (0::Int)+ where bUp   = pin 4+       bDown = pin 2+       led   = pin 13+       update curVal = do+                liftIO $ print curVal+                digitalWrite led (curVal == 0)+                [up, down] <- waitAnyHigh [bUp, bDown]+                let newVal = case (up, down) of+                               (True,  True)  -> curVal    -- simultaneous press+                               (True,  False) -> curVal+1+                               (False, True)  -> curVal-1+                               (False, False) -> curVal    -- can't happen+                update newVal
+ System/Hardware/Arduino/SamplePrograms/LCD.hs view
@@ -0,0 +1,166 @@+-------------------------------------------------------------------------------+-- |+-- Module      :  System.Hardware.Arduino.SamplePrograms.LCD+-- Copyright   :  (c) Levent Erkok+-- License     :  BSD3+-- Maintainer  :  erkokl@gmail.com+-- Stability   :  experimental+--+-- Basic demo of an Hitachi HD44780 LCD+-------------------------------------------------------------------------------++module System.Hardware.Arduino.SamplePrograms.LCD where++import Control.Monad.Trans (liftIO)+import Data.Char           (isSpace)+import Numeric             (showHex)++import System.Hardware.Arduino+import System.Hardware.Arduino.LCD++-- | Connections for a basic hitachi controller.+-- See <http://en.wikipedia.org/wiki/Hitachi_HD44780_LCD_controller> for+-- pin layout. For this demo, simply connect the LCD pins to the Arduino+-- as follows:+--+--  * LCD pin @01@ to GND+--+--  * LCD pin @02@ to +5V+--+--  * LCD pin @03@ to a 10K potentiometer's viper+--+--  * LCD pin @04@ to Arduino pin @12@+--+--  * LCD pin @05@ to GND+--+--  * LCD pin @06@ to Arduino pin @11@+--+--  * LCD pin @11@ to Arduino pin @5@+--+--  * LCD pin @12@ to Arduino pin @4@+--+--  * LCD pin @13@ to Arduino pin @3@+--+--  * LCD pin @14@ to Arduino pin @2@+--+--  * [If backlight is needed] LCD pin @15@ to +5V+--+--  * [If backlight is needed] LCD pin @16@ to GND via 220ohm resistor+--+--  <<http://github.com/LeventErkok/hArduino/raw/master/System/Hardware/Arduino/SamplePrograms/Schematics/LCD.png>>+hitachi :: LCDController+-- Connections:                   ARDUINO     Hitachi   Description+--------------------------------  -------    ---------  ----------------+hitachi = Hitachi44780 { lcdRS   = pin 12  --     4      Register-select+                       , lcdEN   = pin 11  --     6      Enable+                       , lcdD4   = pin  5  --    11      Data 4+                       , lcdD5   = pin  4  --    12      Data 5+                       , lcdD6   = pin  3  --    13      Data 6+                       , lcdD7   = pin  2  --    14      Data 7+                       -- Other config variables for the display+                       , lcdRows     = 2    -- 2 rows+                       , lcdCols     = 16    -- of 16 columns+                       , dotMode5x10 = False -- Using the standard 5x8 dots+                       }++-- | The happy glyph. See 'lcdCreateSymbol' for details on how to create new ones.+happy :: [String]+happy = [ "     "+        , "@   @"+        , "     "+        , "     "+        , "@   @"+        , " @@@ "+        , "     "+        , "     "+        ]++-- | The sad glyph. See 'lcdCreateSymbol' for details on how to create new ones.+sad :: [String]+sad = [ "     "+      , "@   @"+      , "     "+      , "     "+      , "     "+      , " @@@ "+      , "@   @"+      , "     "+      ]++-- | Access the LCD connected to Arduino, making it show messages+-- we read from the user and demonstrate other LCD control features offered+-- by hArduino.+lcdDemo :: IO ()+lcdDemo = withArduino False "/dev/cu.usbmodemfd131" $ do+              lcd <- lcdRegister hitachi+              happySymbol <- lcdCreateSymbol lcd happy+              sadSymbol   <- lcdCreateSymbol lcd sad+              lcdHome lcd+              liftIO $ do putStrLn "Hitachi controller demo.."+                          putStrLn ""+                          putStrLn "Looking for an example? Try the following sequence:"+                          putStrLn "    cursor 5 0"+                          putStrLn "    happy"+                          putStrLn "    write _"+                          putStrLn "    happy"+                          putStrLn "    flash 5"+                          putStrLn ""+                          putStrLn "Type ? to see all available commands."+              let repl = do liftIO $ putStr "LCD> "+                            m <- liftIO getLine+                            case words m of+                              []       -> repl+                              ["quit"] -> return ()+                              (cmd:_)    -> case cmd `lookup` commands of+                                              Nothing        -> do liftIO $ putStrLn $ "Unknown command '" ++ cmd ++ "', type ? for help."+                                                                   repl+                                              Just (_, _, c) -> do c lcd (dropWhile isSpace (drop (length cmd) m)) (happySymbol, sadSymbol)+                                                                   repl+              repl+  where help = liftIO $ do let (cmds, args, hlps) = unzip3 $ ("quit", "", "Quit the demo") : [(c, a, h) | (c, (a, h, _)) <- commands]+                               clen = 1 + maximum (map length cmds)+                               alen = 8 + maximum (map length args)+                               pad l s = take l (s ++ repeat ' ')+                               line (c, a, h) = putStrLn $ pad clen c ++ pad alen a ++ h+                           mapM_ line $ zip3 cmds args hlps+        arg0 f _   [] _ = f+        arg0 _ _   a  _ = liftIO $ putStrLn $ "Unexpected arguments: " ++ show a+        arg1 f lcd [] _ = f lcd+        arg1 _ _   a  _ = liftIO $ putStrLn $ "Unexpected arguments: " ++ show a+        arg2 f lcd a  _ = f lcd a+        arg3            = id+        grabNums n a f  = case [v | [(v, "")] <- map reads (words a)] of+                            vs | length vs /= n -> liftIO $ putStrLn $ "Need " ++ show n ++ " numeric parameter" ++ if n == 1 then "." else "s."+                            vs                  -> f vs+        symbol isHappy lcd _ (h, s) = lcdWriteSymbol lcd (if isHappy then h else s)+        cursor lcd a = grabNums 2 a (\[col, row] -> lcdSetCursor lcd (col, row))+        flash  lcd a = grabNums 1 a (\[n] -> lcdFlash lcd n 500)+        code   lcd a = grabNums 1 a (\[n] -> do lcdClear lcd+                                                lcdHome lcd+                                                lcdWriteSymbol lcd (lcdInternalSymbol n)+                                                lcdWrite lcd $ " (Code: 0x" ++ showHex n "" ++ ")")+        scroll toLeft lcd a = grabNums 1 a (\[n] -> do let scr | toLeft = lcdScrollDisplayLeft+                                                               | True   = lcdScrollDisplayRight+                                                       sequence_ $ concat $ replicate n [scr lcd, delay 500])+        commands = [ ("?",           ("",        "Display this help message",   arg0 help))+                   , ("clear",       ("",        "Clear the LCD screen",        arg1 lcdClear))+                   , ("write",       ("string",  "Write to the LCD",            arg2 lcdWrite))+                   , ("home",        ("",        "Move cursor to home",         arg1 lcdHome))+                   , ("cursor",      ("col row", "Move cursor to col row",      arg2 cursor))+                   , ("scrollOff",   ("",        "Turn off auto-scroll",        arg1 lcdAutoScrollOff))+                   , ("scrollOn",    ("",        "Turn on auto-scroll",         arg1 lcdAutoScrollOn))+                   , ("scrollLeft",  ("n",       "Scroll left by n chars",      arg2 (scroll True)))+                   , ("scrollRight", ("n",       "Scroll right by n char",      arg2 (scroll False)))+                   , ("leftToRight", ("",        "Set left to right direction", arg1 lcdLeftToRight))+                   , ("rightToLeft", ("",        "Set left to right direction", arg1 lcdRightToLeft))+                   , ("blinkOn",     ("",        "Set blinking ON",             arg1 lcdBlinkOn))+                   , ("blinkOff",    ("",        "Set blinking ON",             arg1 lcdBlinkOff))+                   , ("cursorOn",    ("",        "Display the cursor",          arg1 lcdCursorOn))+                   , ("cursorOff",   ("",        "Do not display the cursor",   arg1 lcdCursorOff))+                   , ("displayOn",   ("",        "Turn the display on",         arg1 lcdDisplayOn))+                   , ("displayOff",  ("",        "Turn the display off",        arg1 lcdDisplayOff))+                   , ("flash",       ("n",       "Flash the display n times",   arg2 flash))+                   , ("happy",       ("",        "Draw a smiling face",         arg3 (symbol True)))+                   , ("sad",         ("",        "Draw a sad face",             arg3 (symbol False)))+                   , ("code",        ("n",       "Write symbol with code n",    arg2 code))+                   ]
System/Hardware/Arduino/Utils.hs view
@@ -60,10 +60,6 @@ -- | Turn a lo/hi encoded Arduino string constant into a Haskell string getString :: [Word8] -> String getString []         = ""-getString [a]        = [chr (fromIntegral a)]  -- shouldn't happen, but no need to error out either+getString [a]        = [chr (fromIntegral a)]  -- shouldn't happen getString (l:h:rest) = c : getString rest   where c = chr $ fromIntegral $ h `shiftL` 8 .|. l---- | Error out-die :: String -> [String] -> a-die m ms = error $ "\n*** hArduino:ERROR: " ++ intercalate "\n*** " (m:ms)
hArduino.cabal view
@@ -1,5 +1,5 @@ Name:          hArduino-Version:       0.2+Version:       0.3 Category:      Hardware Synopsis:      Control your Arduino board from Haskell. Description:   Control Arduino from Haskell, using the Firmata protocol.@@ -11,6 +11,8 @@                can exchange information with the board, send/receive commands from other                peripherals connected, etc.                .+               A short (4m29s) video of the blinking example: <http://www.youtube.com/watch?v=PPa3im44t2g>+               .                hArduino is work-in-progress. Comments, bug-reports, and patches are welcome. Copyright:     Levent Erkok, 2013 License:       BSD3@@ -22,7 +24,7 @@ Maintainer:    Levent Erkok (erkokl@gmail.com) Build-Type:    Simple Cabal-Version: >= 1.14-Extra-Source-Files: INSTALL, README, COPYRIGHT, RELEASENOTES+Extra-Source-Files: INSTALL, README.md, COPYRIGHT, CHANGES.md  source-repository head     type:       git@@ -37,11 +39,14 @@                   -- threaddelay is broken on Mac. See: http://hackage.haskell.org/trac/ghc/ticket/7299                   , process   Exposed-modules : System.Hardware.Arduino-                  , System.Hardware.Arduino.Examples.Blink-                  , System.Hardware.Arduino.Examples.Switch+                  , System.Hardware.Arduino.LCD+                  , System.Hardware.Arduino.SamplePrograms.Analog+                  , System.Hardware.Arduino.SamplePrograms.Blink+                  , System.Hardware.Arduino.SamplePrograms.Button+                  , System.Hardware.Arduino.SamplePrograms.Counter+                  , System.Hardware.Arduino.SamplePrograms.LCD   Other-modules   : System.Hardware.Arduino.Comm                   , System.Hardware.Arduino.Data                   , System.Hardware.Arduino.Firmata-                  , System.Hardware.Arduino.Firmata.Basics                   , System.Hardware.Arduino.Protocol                   , System.Hardware.Arduino.Utils