-- | This module be used to create a new module targeting a specific -- serial device. See CoPilot.Arduino.Library.Serial and -- CoPilot.Arduino.Library.Serial.XBee for examples. {-# LANGUAGE RebindableSyntax #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} module Copilot.Arduino.Library.Serial.Device ( module Copilot.Arduino.Library.Serial.Device, IsDigitalIOPin, ) where import Copilot.Arduino hiding (show) import Copilot.Arduino.Internals import Control.Monad.Writer import Copilot.Language.Stream (Arg) import Data.List import Data.Maybe import Data.Proxy import qualified Prelude -- | Eg \"Serial\" or \"Serial2\" newtype SerialDeviceName = SerialDeviceName String baudD :: SerialDeviceName -> Int -> Sketch () baudD (SerialDeviceName devname) n = tell [(\_ -> return (), \_ -> f)] where f = mempty { setups = mkCChunk [CLine $ devname <> ".begin(" <> Prelude.show n <> ");"] } newtype Baud = Baud Int deriving (Show, Eq) configureD :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => SerialDeviceName -> Pin rx -> Pin tx -> Baud -> Sketch () configureD d@(SerialDeviceName devname) (Pin (PinId rxpin)) (Pin (PinId txpin)) (Baud n) = do baudD d n tell [(\_ -> return (), \_ -> f)] where f = mempty { defines = [ CChunk [ CLine $ "#include " ] , CChunk [ CLine $ "SoftwareSerial " <> devname <> " = SoftwareSerial" <> "(" <> Prelude.show rxpin <> ", " <> Prelude.show txpin <> ");" ] ] } newtype SerialDevice = SerialDevice SerialDeviceName instance Input SerialDevice Int8 where input' (SerialDevice (SerialDeviceName devname)) interpretvalues = mkInput s where s = InputSource { defineVar = mkCChunk [CLine $ "int " <> varname <> ";"] , setupInput = [] , inputPinmode = mempty , readInput = mkCChunk [CLine $ varname <> " = " <> devname <> ".read();"] , inputStream = extern varname interpretvalues' } varname = "input_" <> devname interpretvalues' | null interpretvalues = Nothing | otherwise = Just interpretvalues -- | Value that is read from serial port when there is no input available. noInput :: Int8 noInput = -1 instance Output SerialDevice [FormatOutput] where sdn =: l = sdn =: (Event l true :: Event () [FormatOutput]) instance Output SerialDevice (Event () [FormatOutput]) where SerialDevice sdn@(SerialDeviceName devname) =: (Event l c) = do u <- getUniqueId "serial" let outputfuncname = uniqueName ("output_" <> devname) u let f = mempty { defines = printer outputfuncname } (f', triggername) <- defineTriggerAlias outputfuncname f tell [(go triggername, \_ -> f')] where go triggername tl = let c' = addTriggerLimit tl c in trigger triggername c' (mapMaybe formatArg l) printer outputfuncname = mkCChunk $ concat [ [CLine $ "void " <> outputfuncname <> "(" <> intercalate ", " arglist <> ") {"] , map (\(fmt, n) -> CLine (" " <> fromCLine (fmt n))) (zip (map (\fo -> formatCLine fo sdn) l) argnames) , [CLine "}"] ] argnames = map (\n -> "arg" <> Prelude.show n) ([1..] :: [Integer]) arglist = mapMaybe mkarg (zip (map formatCType l) argnames) mkarg (Just ctype, argname) = Just (ctype <> " " <> argname) mkarg (Nothing, _) = Nothing instance IsBehavior [FormatOutput] where (@:) = Event type instance BehaviorToEvent [FormatOutput] = Event () [FormatOutput] data FormatOutput = FormatOutput { formatArg :: Maybe Arg , formatCType :: Maybe String , formatCLine :: SerialDeviceName -> String -> CLine } -- | Use this to output a Char. char :: Char -> FormatOutput char c = FormatOutput Nothing Nothing (\(SerialDeviceName devname) _ -> CLine $ devname <> ".print('" <> esc c <> "');") where esc '\'' = "\\\'" esc '\\' = "\\\\" esc '\n' = "\\n" esc c' = [c'] quoteString :: String -> String quoteString s = '"' : concatMap esc s <> "\"" where esc '"' = "\\\"" esc '\\' = "\\\\" esc '\n' = "\\n" esc c = [c] class OutputString t where -- | Use this to output a `String` or `FlashString` str :: t -> FormatOutput instance OutputString String where str s = FormatOutput Nothing Nothing $ \(SerialDeviceName devname) _ -> CLine $ devname <> ".print(" <> quoteString s <> ");" -- | Normally a String will be copied into ram before it is output. -- A FlashString will be output directly from flash memory. -- -- Using this with `str` will reduce the amount of memory used by your -- program, but will likely slightly increase the size of the program. newtype FlashString = FlashString String instance OutputString FlashString where str (FlashString s) = FormatOutput Nothing Nothing $ \(SerialDeviceName devname) _ -> CLine $ devname <> ".print(F(" <> quoteString s <> "));" -- | Use this to show the current value of a Stream. -- -- Numbers will be formatted in decimal. Bool is displayed as 0 and 1. show :: forall t. (ShowCType t, Typed t) => Stream t -> FormatOutput show s = FormatOutput (Just (arg s)) (Just (showCType (Proxy @t))) (\(SerialDeviceName devname) v -> CLine $ devname <> ".print(" <> v <> ");") -- | Write a byte to the serial port. byte :: Stream Int8 -> FormatOutput byte s = FormatOutput (Just (arg s)) (Just (showCType (Proxy @Int8))) (\(SerialDeviceName devname) v -> CLine $ devname <> ".write(" <> v <> ");") -- | Show the current value of a Stream with control over the formatting. -- -- When used with a Float, provide the number of decimal places -- to show. -- -- > Serial.showFormatted (constant (1.234 :: Float)) 2 -- "1.23" -- -- When used with any Integral type, provide the `Base` to display it in -- -- > Serial.showFormatted (constant (78 :: Int8)) Serial.HEX -- "4E" showFormatted :: forall t f. (ShowCType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput showFormatted s f = FormatOutput (Just (arg s)) (Just (showCType t)) (\(SerialDeviceName devname) v -> CLine $ devname <> ".print(" <> v <> ", " <> formatter t f <> ");") where t = Proxy @t class FormatableType t f where formatter :: Proxy t -> f -> String instance FormatableType Float Int where formatter _ precision = Prelude.show precision instance Integral t => FormatableType t Base where formatter _ b = Prelude.show b data Base = BIN | OCT | DEC | HEX deriving (Show)