arduino-copilot 1.1.1 → 1.2.0
raw patch · 11 files changed
+490/−269 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Copilot.Arduino: AnalogInput :: PinCapabilities
- Copilot.Arduino: DigitalIO :: PinCapabilities
- Copilot.Arduino: PWM :: PinCapabilities
- Copilot.Arduino: data Output t
- Copilot.Arduino: data PinCapabilities
- Copilot.Arduino: pwm :: IsPWMPin t => Pin t -> Output DutyCycle
- Copilot.Arduino: type DutyCycle = Word8
- Copilot.Arduino: type MicroSeconds = Int16
- Copilot.Arduino: writeto :: IsDigitalIOPin t => Pin t -> Output Bool
- Copilot.Arduino.Internals: Output :: [CLine] -> Map PinId PinMode -> Stream Bool -> (Stream Bool -> Behavior t) -> Output t
- Copilot.Arduino.Internals: [outputBehavior] :: Output t -> Stream Bool -> Behavior t
- Copilot.Arduino.Internals: [outputCond] :: Output t -> Stream Bool
- Copilot.Arduino.Internals: [outputPinmode] :: Output t -> Map PinId PinMode
- Copilot.Arduino.Internals: [setupOutput] :: Output t -> [CLine]
- Copilot.Arduino.Internals: class ToFramework t
- Copilot.Arduino.Internals: data Output t
- Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ToFramework (Copilot.Arduino.Internals.InputSource t)
- Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.ToFramework (Copilot.Arduino.Internals.Output t)
- Copilot.Arduino.Internals: toFramework :: ToFramework t => t -> Framework
- Copilot.Arduino.Internals: type MicroSeconds = Int16
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.FormatableType GHC.Types.Float GHC.Types.Int
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int16
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int32
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int64
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Int.Int8
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Types.Bool
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Types.Double
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Types.Float
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word16
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word32
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word64
- Copilot.Arduino.Library.Serial: instance Copilot.Arduino.Library.Serial.ShowableType GHC.Word.Word8
- Copilot.Arduino.Library.Serial: instance GHC.Real.Integral t => Copilot.Arduino.Library.Serial.FormatableType t Copilot.Arduino.Library.Serial.Base
- Copilot.Arduino.Library.Serial: instance GHC.Show.Show Copilot.Arduino.Library.Serial.Base
+ Copilot.Arduino: MicroSeconds :: Stream Word16 -> MicroSeconds
+ Copilot.Arduino: MilliSeconds :: Stream Word16 -> MilliSeconds
+ Copilot.Arduino: PWMDutyCycle :: Behavior Word8 -> PWMDutyCycle
+ Copilot.Arduino: class Output o t
+ Copilot.Arduino: data Event v
+ Copilot.Arduino: data MicroSeconds
+ Copilot.Arduino: data MilliSeconds
+ Copilot.Arduino: data PWMDutyCycle
+ Copilot.Arduino: instance Copilot.Arduino.Internals.IsPWMPin t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Internals.Pin t) (Copilot.Arduino.Internals.Event Copilot.Arduino.PWMDutyCycle)
+ Copilot.Arduino: instance Copilot.Arduino.Internals.IsPWMPin t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Internals.Pin t) Copilot.Arduino.PWMDutyCycle
+ Copilot.Arduino: instance Copilot.Arduino.Internals.Output Copilot.Arduino.Delay Copilot.Arduino.MicroSeconds
+ Copilot.Arduino: instance Copilot.Arduino.Internals.Output Copilot.Arduino.Delay Copilot.Arduino.MilliSeconds
+ Copilot.Arduino: type Behavior t = Stream t
+ Copilot.Arduino.Internals: (=:) :: Output o t => o -> t -> Sketch ()
+ Copilot.Arduino.Internals: (@:) :: v -> Behavior Bool -> Event v
+ Copilot.Arduino.Internals: Event :: v -> Stream Bool -> Event v
+ Copilot.Arduino.Internals: alwaysEvent :: v -> Event v
+ Copilot.Arduino.Internals: class Output o t
+ Copilot.Arduino.Internals: data Event v
+ Copilot.Arduino.Internals: defineTriggerAlias :: String -> String -> Framework -> (Framework, String)
+ Copilot.Arduino.Internals: infixr 1 =:
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsDigitalIOPin t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Internals.Pin t) (Copilot.Arduino.Internals.Event (Copilot.Arduino.Internals.Behavior GHC.Types.Bool))
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.Output o (Copilot.Arduino.Internals.Event (Copilot.Arduino.Internals.Behavior v)) => Copilot.Arduino.Internals.Output o (Copilot.Arduino.Internals.Behavior v)
+ Copilot.Arduino.Library.Serial: byte :: Stream Int8 -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: BIN :: Base
+ Copilot.Arduino.Library.Serial.Device: Baud :: Int -> Baud
+ Copilot.Arduino.Library.Serial.Device: DEC :: Base
+ Copilot.Arduino.Library.Serial.Device: FormatOutput :: Maybe Arg -> Maybe String -> (SerialDeviceName -> String -> CLine) -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: HEX :: Base
+ Copilot.Arduino.Library.Serial.Device: OCT :: Base
+ Copilot.Arduino.Library.Serial.Device: SerialDeviceName :: String -> SerialDeviceName
+ Copilot.Arduino.Library.Serial.Device: [formatArg] :: FormatOutput -> Maybe Arg
+ Copilot.Arduino.Library.Serial.Device: [formatCLine] :: FormatOutput -> SerialDeviceName -> String -> CLine
+ Copilot.Arduino.Library.Serial.Device: [formatCType] :: FormatOutput -> Maybe String
+ Copilot.Arduino.Library.Serial.Device: baudD :: SerialDeviceName -> Int -> Sketch ()
+ Copilot.Arduino.Library.Serial.Device: byte :: Stream Int8 -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: char :: Char -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: class FormatableType t f
+ Copilot.Arduino.Library.Serial.Device: class ShowableType t
+ Copilot.Arduino.Library.Serial.Device: data Base
+ Copilot.Arduino.Library.Serial.Device: data FormatOutput
+ Copilot.Arduino.Library.Serial.Device: deviceD :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => SerialDeviceName -> Pin rx -> Pin tx -> Baud -> Sketch ()
+ Copilot.Arduino.Library.Serial.Device: formatter :: FormatableType t f => Proxy t -> f -> String
+ Copilot.Arduino.Library.Serial.Device: input'D :: SerialDeviceName -> [Int8] -> Input Int8
+ Copilot.Arduino.Library.Serial.Device: inputD :: SerialDeviceName -> Input Int8
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.FormatableType GHC.Types.Float GHC.Types.Int
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int16
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int32
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int64
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Int.Int8
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Types.Bool
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Types.Double
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Types.Float
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word16
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word32
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word64
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Library.Serial.Device.ShowableType GHC.Word.Word8
+ Copilot.Arduino.Library.Serial.Device: instance GHC.Classes.Eq Copilot.Arduino.Library.Serial.Device.Baud
+ Copilot.Arduino.Library.Serial.Device: instance GHC.Real.Integral t => Copilot.Arduino.Library.Serial.Device.FormatableType t Copilot.Arduino.Library.Serial.Device.Base
+ Copilot.Arduino.Library.Serial.Device: instance GHC.Show.Show Copilot.Arduino.Library.Serial.Device.Base
+ Copilot.Arduino.Library.Serial.Device: instance GHC.Show.Show Copilot.Arduino.Library.Serial.Device.Baud
+ Copilot.Arduino.Library.Serial.Device: newtype Baud
+ Copilot.Arduino.Library.Serial.Device: newtype SerialDeviceName
+ Copilot.Arduino.Library.Serial.Device: noInput :: Int8
+ Copilot.Arduino.Library.Serial.Device: outputD :: SerialDeviceName -> Stream Bool -> [FormatOutput] -> Sketch ()
+ Copilot.Arduino.Library.Serial.Device: show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: showCType :: ShowableType t => Proxy t -> String
+ Copilot.Arduino.Library.Serial.Device: showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: str :: String -> FormatOutput
+ Copilot.Arduino.Library.Serial.Device: type family IsDigitalIOPin t
+ Copilot.Arduino.Library.Serial.XBee: BIN :: Base
+ Copilot.Arduino.Library.Serial.XBee: Baud :: Int -> Baud
+ Copilot.Arduino.Library.Serial.XBee: DEC :: Base
+ Copilot.Arduino.Library.Serial.XBee: HEX :: Base
+ Copilot.Arduino.Library.Serial.XBee: OCT :: Base
+ Copilot.Arduino.Library.Serial.XBee: byte :: Stream Int8 -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: char :: Char -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: class FormatableType t f
+ Copilot.Arduino.Library.Serial.XBee: class ShowableType t
+ Copilot.Arduino.Library.Serial.XBee: data Base
+ Copilot.Arduino.Library.Serial.XBee: device :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => Pin rx -> Pin tx -> Baud -> Sketch ()
+ Copilot.Arduino.Library.Serial.XBee: input :: Input Int8
+ Copilot.Arduino.Library.Serial.XBee: input' :: [Int8] -> Input Int8
+ Copilot.Arduino.Library.Serial.XBee: newtype Baud
+ Copilot.Arduino.Library.Serial.XBee: noInput :: Int8
+ Copilot.Arduino.Library.Serial.XBee: output :: Stream Bool -> [FormatOutput] -> Sketch ()
+ Copilot.Arduino.Library.Serial.XBee: show :: forall t. (ShowableType t, Typed t) => Stream t -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: str :: String -> FormatOutput
- Copilot.Arduino: (=:) :: Output t -> Stream t -> Sketch ()
+ Copilot.Arduino: (=:) :: Output o t => o -> t -> Sketch ()
- Copilot.Arduino: (@:) :: Output t -> Stream Bool -> Output t
+ Copilot.Arduino: (@:) :: v -> Behavior Bool -> Event v
- Copilot.Arduino: blinking :: Stream Bool
+ Copilot.Arduino: blinking :: Behavior Bool
- Copilot.Arduino: delay :: Output MicroSeconds
+ Copilot.Arduino: delay :: Delay
- Copilot.Arduino: firstIteration :: Stream Bool
+ Copilot.Arduino: firstIteration :: Behavior Bool
- Copilot.Arduino: led :: Output Bool
+ Copilot.Arduino: led :: Pin '[ 'DigitalIO]
- Copilot.Arduino.Internals: type Behavior t = Stream t -> Spec
+ Copilot.Arduino.Internals: type Behavior t = Stream t
- Copilot.Arduino.Internals: type family SameCapability (a :: PinCapabilities) (b :: PinCapabilities) :: Bool
+ Copilot.Arduino.Internals: type family SameCapability a b :: Bool
Files
- CHANGELOG +13/−0
- TODO +29/−0
- arduino-copilot.cabal +3/−1
- examples/blink/demo.hs +1/−1
- examples/button/demo.hs +3/−3
- src/Copilot/Arduino.hs +56/−92
- src/Copilot/Arduino/Internals.hs +88/−39
- src/Copilot/Arduino/Library/Serial.hs +10/−132
- src/Copilot/Arduino/Library/Serial/Device.hs +208/−0
- src/Copilot/Arduino/Library/Serial/XBee.hs +78/−0
- src/Copilot/Arduino/Main.hs +1/−1
CHANGELOG view
@@ -1,3 +1,16 @@+arduino-copilot (1.2.0) unstable; urgency=medium++ * Bugfix: Doing the same kind of write to two different pins in the+ same program caused the C generated by Copilot to not compile.+ * Several changes making the API more FRP style, including adding+ Behavior and Event types.+ * Removed writeto and pwm, instead the type of a value connected to a pin+ with =: determines how the pin is driven.+ * delay now takes a value in MilliSeconds or MicroSeconds.+ * Generalized serial port interface, added support for XBee.++ -- Joey Hess <id@joeyh.name> Tue, 28 Jan 2020 20:39:11 -0400+ arduino-copilot (1.1.1) unstable; urgency=medium * New Copilot.Arduino.Library.Serial module.
TODO view
@@ -1,3 +1,31 @@+* Currently all Inputs run each time through the loop, which could be a+ problem when an input is slow. It would be good if the @: operator could+ also rate limit when Inputs occur.++ Copilot does not let `extern` be limited, but the actual input operation+ is done by ardino-copilot's readInput, and extern then just uses the+ value it sets. So, make each Input add a trigger, that sets a flag+ variable, and in readInput just check if the flag is set.++ It should be possible to implement this w/o adding overhead when @: is+ not used on an Input. Either, avoid checking the flag in this case,+ or check that the C compiler can optimise away a check of a flag that+ always succeeds.++* Naming could be more FRP-like. readfrom and readvoltage+ are too imperative sounding. (Outputs were already fixed to be+ FRP-style.)++ A polymorphic read operation could work, assuming type inference is+ able to usually determine what kind of value is wanted to be read.++ a <- stream a1+ pin1 := a++ Looks like this would work: While the type of a could be Bool or Voltage,+ going to a pin that only supports digital IO implies it must be a Bool.+ (Need to check if the type checker is able to work this out!)+ * analogReference() Takes one of a set of defines, which vary depending on board, so how to pass that through Copilot? Could write a C@@ -7,5 +35,6 @@ to a floating point actual voltage, given also a stream that contains the reference voltage. * delayMicroseconds+* pulses * random numbers * Wire library
arduino-copilot.cabal view
@@ -1,5 +1,5 @@ Name: arduino-copilot-Version: 1.1.1+Version: 1.2.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -51,6 +51,8 @@ Copilot.Arduino.Uno Copilot.Arduino.Internals Copilot.Arduino.Library.Serial+ Copilot.Arduino.Library.Serial.Device+ Copilot.Arduino.Library.Serial.XBee Other-Modules: Copilot.Arduino.Main Build-Depends:
examples/blink/demo.hs view
@@ -5,4 +5,4 @@ main :: IO () main = arduino $ do led =: blinking- delay =: constant 100+ delay =: MilliSeconds (constant 100)
examples/button/demo.hs view
@@ -2,10 +2,10 @@ import Copilot.Arduino.Uno -longer_and_longer :: Stream Int16+longer_and_longer :: Stream Word16 longer_and_longer = counter true $ counter true false `mod` 64 == 0 -counter :: Stream Bool -> Stream Bool -> Stream Int16+counter :: Stream Bool -> Stream Bool -> Stream Word16 counter inc reset = cnt where cnt = if reset@@ -19,4 +19,4 @@ main = arduino $ do buttonpressed <- readfrom' pin12 [False, False, False, True, True] led =: buttonpressed || blinking- delay =: longer_and_longer * 2+ delay =: MilliSeconds (longer_and_longer * 2)
src/Copilot/Arduino.hs view
@@ -1,4 +1,4 @@--- | Programming the Arduino with Copilot.+-- | Programming the Arduino with Copilot, in functional reactive style. -- -- This module should work on any model of Arduino. -- See Copilot.Arduino.Uno and Copilot.Arduino.Nano for model-specific code.@@ -10,27 +10,21 @@ {-# LANGUAGE DataKinds #-} {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-} module Copilot.Arduino ( -- * Arduino sketch generation arduino, Sketch,- Input,- Output,- -- | A value that varies over time.- --- -- The Copilot DSL provides many operations on streams, for example- -- `Language.Copilot.&&` to combine two streams of Bools.- -- - -- For documentation on using the Copilot DSL, see- -- <https://copilot-language.github.io/>- Stream, Pin,- PinCapabilities(..),- -- * Combinators- (=:),+ -- * Functional reactive programming+ Behavior,+ Event, (@:), -- * Inputs+ Input, readfrom, readfrom', pullup,@@ -39,15 +33,16 @@ readvoltage', -- * Outputs --- -- | Only outputs that work on all Arduino boards are included- -- here. Import a module such as Copilot.Arduino.Uno for- -- model-specific outputs.+ -- | Only a few common outputs are included in this module.+ -- Import a module such as Copilot.Arduino.Uno for `Pin`+ -- definitions etc.+ Output, led,- writeto,- DutyCycle,- pwm,- MicroSeconds,+ (=:),+ PWMDutyCycle(..), delay,+ MilliSeconds(..),+ MicroSeconds(..), -- * Utilities blinking, firstIteration,@@ -63,6 +58,7 @@ -- -- For documentation on using the Copilot DSL, see -- <https://copilot-language.github.io/>+ Stream, module X, ) where @@ -74,14 +70,6 @@ import qualified Data.Map as M import qualified Data.Set as S --- | Connect a `Stream` to an `Output`.------ > led =: blinking-(=:) :: Output t -> Stream t -> Sketch ()-o =: s = tell [(go, toFramework o)]- where- go = (outputBehavior o) (outputCond o) s- -- | Use this to make a LED blink on and off. -- -- On each iteration of the `Sketch`, this changes to the opposite of its@@ -92,51 +80,40 @@ -- combinators. -- -- > blinking = clk (period 2) (phase 1)-blinking :: Stream Bool+blinking :: Behavior Bool blinking = clk (period (2 :: Integer)) (phase (1 :: Integer)) --- Same fixity as =<<-infixr 1 =:---- | By default, an `Output` is written to on each iteration of the `Sketch`.------ For example, this constantly turns on the LED, even though it will--- already be on after the first iteration.------ > led =: true------ To avoid unnecessary work being done, this combinator can make an--- `Output` only be written to when the current value of the provided--- `Stream` is True.------ So to make the LED only be turned on in the first iteration,--- and allow it to remain on thereafter without doing extra work:------ > led @: firstIteration =: true-(@:) :: Output t -> Stream Bool -> Output t-(@:) o c = o { outputCond = c }- -- | True on the first iteration of the `Sketch`, and False thereafter.-firstIteration :: Stream Bool+firstIteration :: Behavior Bool firstIteration = [True]++false +-- | A stream of milliseconds.+data MilliSeconds = MilliSeconds (Stream Word16)++-- | A stream of microseconds.+data MicroSeconds = MicroSeconds (Stream Word16)+ -- | Use this to add a delay between each iteration of the `Sketch`. -- A `Sketch` with no delay will run as fast as the hardware can run it. ----- > delay =: constant 100-delay :: Output MicroSeconds-delay = Output- { setupOutput = []- , outputBehavior = \c n -> trigger "delay" c [arg n]- , outputCond = true- , outputPinmode = mempty- }+-- > delay := MilliSeconds (constant 100)+delay :: Delay+delay = Delay +data Delay = Delay++instance Output Delay MilliSeconds where+ Delay =: (MilliSeconds n) = tell+ [(trigger "delay" true [arg n], mempty)]++instance Output Delay MicroSeconds where+ Delay =: (MicroSeconds n) = tell+ [(trigger "delayMicroseconds" true [arg n], mempty)]+ -- | Reading a Bool from a `Pin`. ----- > do--- > buttonpressed <- readfrom pin12--- > led =: buttonpressed+-- > buttonpressed <- readfrom pin12+-- > led =: buttonpressed readfrom :: IsDigitalIOPin t => Pin t -> Input Bool readfrom p = readfrom' p [] @@ -191,41 +168,28 @@ | null interpretvalues = Nothing | otherwise = Just interpretvalues --- | Writing to a pin.+-- | Use this to do PWM output to a pin. ----- > writeto pin3 =: blinking-writeto :: IsDigitalIOPin t => Pin t -> Output Bool-writeto (Pin p@(PinId n)) = Output- { setupOutput = []- , outputPinmode = M.singleton p OutputMode- , outputBehavior = - \c v -> trigger "digitalWrite" c [arg (constant n), arg v]- , outputCond = true- }+-- > pin3 =: PWMDutyCycle (constant 128)+-- +-- Each Word8 from the Stream describes a PWM square wave.+-- 0 is always off and 255 is always on.+data PWMDutyCycle = PWMDutyCycle (Behavior Word8) --- | PWM output to a pin.------ > pwm pin3 =: constant 128-pwm :: IsPWMPin t => Pin t -> Output DutyCycle-pwm (Pin (PinId n)) = Output- { setupOutput = []- , outputPinmode = mempty -- analogWrite does not need pinMode- , outputBehavior =- \c v -> trigger "analogWrite" c [arg (constant n), arg v]- , outputCond = true- }+instance IsPWMPin t => Output (Pin t) (Event PWMDutyCycle) where+ (Pin (PinId n)) =: (Event (PWMDutyCycle v) c) = tell [(go, f)]+ where+ go = trigger triggername c [arg (constant n), arg v]+ -- analogWrite does not need any pinmodes set up+ (f, triggername) = defineTriggerAlias (show n) "analogWrite" mempty --- | Describes a PWM square wave. Between 0 (always off) and 255 (always on).-type DutyCycle = Word8+instance IsPWMPin t => Output (Pin t) PWMDutyCycle where+ (=:) o s = o =: alwaysEvent s -- | The on-board LED.-led :: Output Bool-led = writeto p- where- p :: Pin '[ 'DigitalIO ]- p = Pin (PinId 13)+led :: Pin '[ 'DigitalIO ]+led = Pin (PinId 13) --- | An 8 bit character. -- | Extracts a copilot `Spec` from a `Sketch`. -- -- This can be useful to intergrate with other libraries
src/Copilot/Arduino/Internals.hs view
@@ -7,7 +7,9 @@ {-# LANGUAGE PolyKinds #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ScopedTypeVariables #-} module Copilot.Arduino.Internals where @@ -18,15 +20,26 @@ import Data.Type.Bool import GHC.TypeLits +-- | A value that changes over time.+--+-- This is implemented as a `Stream` in the Copilot DSL.+-- Copilot provides many operations on streams, for example+-- `Language.Copilot.&&` to combine two streams of Bools.+-- +-- For documentation on using the Copilot DSL, see+-- <https://copilot-language.github.io/>+type Behavior t = Stream t+ -- | An Arduino sketch, implemented using Copilot. -- -- It's best to think of the `Sketch` as a description of the state of the -- Arduino at any point in time. -- -- Under the hood, the `Sketch` is run in a loop. On each iteration, it first--- reads all inputs and then updates outputs as needed. While it is a monad,--- a Sketch's outputs are not updated in any particular order, because--- Copilot does not guarantee any order.+-- reads all inputs and then updates outputs as needed.+--+-- While it is a monad, a Sketch's outputs are not updated in any+-- particular order, because Copilot does not guarantee any order. newtype Sketch t = Sketch (Writer [(Spec, Framework)] t) deriving (Monad, Applicative, Functor, MonadWriter [(Spec, Framework)]) @@ -62,29 +75,6 @@ instance Monoid Framework where mempty = Framework mempty mempty mempty mempty -class ToFramework t where- toFramework :: t -> Framework--type Behavior t = Stream t -> Spec---- | Somewhere that a Stream can be directed to, in order to control the--- Arduino.-data Output t = Output- { setupOutput :: [CLine]- -- ^ How to set up the output, not including pin mode.- , outputPinmode :: M.Map PinId PinMode- , outputCond :: Stream Bool- , outputBehavior :: Stream Bool -> Behavior t- }--instance ToFramework (Output t) where- toFramework o = Framework- { defines = mempty- , setups = setupOutput o- , pinmodes = M.map S.singleton (outputPinmode o)- , loops = mempty- }- -- | A source of a `Stream` of values input from the Arduino. -- -- Runs in the `Sketch` monad.@@ -103,28 +93,26 @@ , inputStream :: Stream t } -instance ToFramework (InputSource t) where- toFramework i = Framework+mkInput :: InputSource t -> Input t+mkInput i = do+ tell [(return (), f)]+ return (inputStream i)+ where+ f = Framework { defines = defineVar i , setups = setupInput i , pinmodes = M.map S.singleton (inputPinmode i) , loops = readInput i } -mkInput :: InputSource t -> Input t-mkInput i = do- tell [(return (), toFramework i)]- return (inputStream i)- -- | A pin on the Arduino board. -- -- For definitions of pins like `Copilot.Arduino.Uno.pin12`, -- load a module such as Copilot.Arduino.Uno, which provides the pins of a -- particular board. ----- Some pins can only be used for digital IO, while others support--- analog input and/or digital IO. A type-level list of PinCapabilties--- indicates how a Pin can be used.+-- A type-level list indicates how a Pin can be used, so the haskell+-- compiler will detect impossible uses of pins. newtype Pin t = Pin PinId deriving (Show, Eq, Ord) @@ -159,7 +147,7 @@ HasPinCapability c '[] = 'False HasPinCapability c (x ': xs) = SameCapability c x || HasPinCapability c xs -type family SameCapability (a :: PinCapabilities) (b :: PinCapabilities) :: Bool where+type family SameCapability a b :: Bool where SameCapability 'DigitalIO 'DigitalIO = 'True SameCapability 'AnalogInput 'AnalogInput = 'True SameCapability 'PWM 'PWM = 'True@@ -168,5 +156,66 @@ data PinMode = InputMode | InputPullupMode | OutputMode deriving (Show, Eq, Ord) --- FIXME should be a newtype, but how to make a stream of a newtype?-type MicroSeconds = Int16+-- | Things that can have a `Behavior` or `Event` output to them.+class Output o t where+ (=:) :: o -> t -> Sketch ()+ -- ^ Conneact a `Behavior` or `Event` to an `Output`+ --+ -- > led =: blinking+ --+ -- When a `Behavior` is used, its current value is written on each+ -- iteration of the `Sketch`. + --+ -- For example, this constantly turns on the LED, even though it will+ -- already be on after the first iteration, because `true`+ -- is a `Behavior` (that is always True).+ --+ -- > led =: true+ --+ -- To avoid unncessary work being done, you can use an `Event`+ -- instead. Then only new values of the `Event` will be written.+ -- + -- So to make the LED only be turned on in the first iteration,+ -- and allow it to remain on thereafter without doing extra work:+ -- + -- > led =: true @: firstIteration++-- Same fixity as =<<+infixr 1 =:++-- | A discrete event, that occurs at particular points in time.+data Event v = Event v (Stream Bool)++instance Output o (Event (Behavior v)) => Output o (Behavior v) where+ (=:) o s = o =: alwaysEvent s++alwaysEvent :: v -> Event v+alwaysEvent s = Event s true++-- | Generate an event, that only occurs when the `Behavior` Bool is True.+--+-- While `v` is usually some type of `Behavior`, this can also be used with+-- some other data types that contain a `Behavior`. For example:+--+-- > pin3 := PWMDutyCycle (constant 128) @: firstIteration+(@:) :: v -> Behavior Bool -> Event v+(@:) = Event++instance IsDigitalIOPin t => Output (Pin t) (Event (Behavior Bool)) where+ (Pin p@(PinId n)) =: (Event b c) = tell [(go, f)]+ where+ go = trigger triggername c [arg (constant n), arg b]+ (f, triggername) = + defineTriggerAlias (show n) "digitalWrite" $+ mempty { pinmodes = M.singleton p (S.singleton OutputMode) }++-- | Copilot only supports calling a trigger with a given name once+-- per Spec; the generated C code will fail to build if the same name is+-- used in two triggers. This generates a name from a suffix, which should+-- be somehow unique.+defineTriggerAlias :: String -> String -> Framework -> (Framework, String)+defineTriggerAlias suffix cfuncname f = + (f { defines = define : defines f }, triggername)+ where+ triggername = cfuncname <> "_" <> suffix+ define = CLine $ "#define " <> triggername <> " " <> cfuncname
src/Copilot/Arduino/Library/Serial.hs view
@@ -2,14 +2,8 @@ -- -- This module is designed to be imported qualified as Serial -{-# LANGUAGE RebindableSyntax #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TypeApplications #-} module Copilot.Arduino.Library.Serial ( baud,@@ -18,6 +12,7 @@ str, show, showFormatted,+ byte, input, input', noInput,@@ -27,23 +22,17 @@ ) 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+import Copilot.Arduino.Library.Serial.Device+import Prelude () +dev :: SerialDeviceName+dev = SerialDeviceName "Serial"+ -- | Configure the baud rate of the serial port. -- -- This must be included in your sketch if it uses the serial port. baud :: Int -> Sketch ()-baud n = tell [(return (), f)]- where- f = mempty- { setups = [CLine $ "Serial.begin(" <> Prelude.show n <> ");"]- }+baud = baudD dev -- | Output to the serial port. --@@ -65,102 +54,7 @@ -- ^ This Stream controls when output is sent to the serial port. -> [FormatOutput] -> Sketch ()-output c l = tell [(go, f)]- where- go = trigger "arduino_serial_output" c (mapMaybe formatArg l)- f = mempty { defines = printer }-- printer = concat- [ [CLine $ "void arduino_serial_output("- <> intercalate ", " arglist <> ") {"]- , map (\(fmt, n) -> CLine (" " <> fromCLine (fmt n)))- (zip (map formatCLine 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--data FormatOutput = FormatOutput- { formatArg :: Maybe Arg- , formatCType :: Maybe String- , formatCLine :: String -> CLine- }---- | Use this to output a Char-char :: Char -> FormatOutput-char c = FormatOutput Nothing Nothing- (\_ -> CLine $ "Serial.print('" <> esc c <> "');")- where- esc '\'' = "\\\'"- esc '\\' = "\\\\"- esc '\n' = "\\n"- esc c' = [c']---- | Use this to output a String-str :: String -> FormatOutput-str s = FormatOutput Nothing Nothing- (\_ -> CLine $ "Serial.print(\"" <> concatMap esc s <> "\");")- where- esc '"' = "\""- esc '\\' = "\\"- esc c = [c]---- | 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. (ShowableType t, Typed t) => Stream t -> FormatOutput-show s = FormatOutput- (Just (arg s))- (Just (showCType (Proxy @t)))- (\v -> CLine $ "Serial.print(" <> 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.show (constant (1.234 :: Float)) 2 -- "1.23"------ When used with any Integral type, provide the `Base` to display it in------ > Serial.show (constant (78 :: Int8)) Serial.HEX -- "4E"-showFormatted :: forall t f. (ShowableType t, Typed t, FormatableType t f) => Stream t -> f -> FormatOutput-showFormatted s f = FormatOutput- (Just (arg s))- (Just (showCType t))- (\v -> CLine $ "Serial.print(" <> v <> ", " <> formatter t f <> ");")- where- t = Proxy @t--class ShowableType t where- showCType :: Proxy t -> String--instance ShowableType Bool where showCType _ = "bool"-instance ShowableType Int8 where showCType _ = "int8_t"-instance ShowableType Int16 where showCType _ = "int16_t"-instance ShowableType Int32 where showCType _ = "int32_t"-instance ShowableType Int64 where showCType _ = "int64_t"-instance ShowableType Word8 where showCType _ = "uint8_t"-instance ShowableType Word16 where showCType _ = "uint16_t"-instance ShowableType Word32 where showCType _ = "uint32_t"-instance ShowableType Word64 where showCType _ = "uint64_t"-instance ShowableType Float where showCType _ = "float"-instance ShowableType Double where showCType _ = "double"--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)+output = outputD dev -- | Input from the serial port. --@@ -169,24 +63,8 @@ -- -- > userinput <- Serial.input input :: Input Int8-input = input' []+input = inputD dev -- | The list is used to simulate serial input when interpreting the program. input' :: [Int8] -> Input Int8-input' interpretvalues = mkInput $ InputSource- { defineVar = [CLine $ "int " <> varname <> ";"]- , setupInput = []- , inputPinmode = mempty- , readInput = [CLine $ varname <> " = Serial.read();"]- , inputStream = extern varname interpretvalues'- }- where- varname = "arduino_serial_read"- interpretvalues'- | null interpretvalues = Nothing- | otherwise = Just interpretvalues---- | Value that is read from serial port when there is no input available.-noInput :: Int8-noInput = -1-+input' = input'D dev
+ src/Copilot/Arduino/Library/Serial/Device.hs view
@@ -0,0 +1,208 @@+-- | 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 = [CLine $ devname <> ".begin(" <> Prelude.show n <> ");"]+ }++newtype Baud = Baud Int+ deriving (Show, Eq)++deviceD+ :: (IsDigitalIOPin rx, IsDigitalIOPin tx)+ => SerialDeviceName+ -> Pin rx+ -> Pin tx+ -> Baud+ -> Sketch ()+deviceD d@(SerialDeviceName devname) (Pin (PinId rxpin)) (Pin (PinId txpin)) (Baud n) = do+ baudD d n+ tell [(return (), f)]+ where+ f = mempty+ { defines = + [ CLine $ "#include <SoftwareSerial.h>"+ , CLine $ "SoftwareSerial " <> devname+ <> " = SoftwareSerial"+ <> "("+ <> Prelude.show rxpin+ <> ", "+ <> Prelude.show txpin+ <> ");"+ ]+ }++outputD+ :: SerialDeviceName+ -> Stream Bool+ -- ^ This Stream controls when output is sent to the serial port.+ -> [FormatOutput]+ -> Sketch ()+outputD sdn@(SerialDeviceName devname) c l = tell [(go, f)]+ where+ go = trigger triggername c (mapMaybe formatArg l)+ f = mempty { defines = printer }++ triggername = "arduino_serial_" <> devname <> "_output"++ printer = concat+ [ [CLine $ "void " <> triggername <> "("+ <> 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++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']++-- | Use this to output a String+str :: String -> FormatOutput+str s = FormatOutput Nothing Nothing+ (\(SerialDeviceName devname) _ ->+ CLine $ devname <> ".print(\"" <> concatMap esc s <> "\");")+ where+ esc '"' = "\""+ esc '\\' = "\\"+ esc '\n' = "\\\n"+ esc c = [c]++-- | 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. (ShowableType 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. (ShowableType 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 ShowableType t where+ showCType :: Proxy t -> String++instance ShowableType Bool where showCType _ = "bool"+instance ShowableType Int8 where showCType _ = "int8_t"+instance ShowableType Int16 where showCType _ = "int16_t"+instance ShowableType Int32 where showCType _ = "int32_t"+instance ShowableType Int64 where showCType _ = "int64_t"+instance ShowableType Word8 where showCType _ = "uint8_t"+instance ShowableType Word16 where showCType _ = "uint16_t"+instance ShowableType Word32 where showCType _ = "uint32_t"+instance ShowableType Word64 where showCType _ = "uint64_t"+instance ShowableType Float where showCType _ = "float"+instance ShowableType Double where showCType _ = "double"++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)++inputD :: SerialDeviceName -> Input Int8+inputD d = input'D d []++-- | The list is used to simulate serial input when interpreting the program.+input'D :: SerialDeviceName -> [Int8] -> Input Int8+input'D (SerialDeviceName devname) interpretvalues = mkInput $ InputSource+ { defineVar = [CLine $ "int " <> varname <> ";"]+ , setupInput = []+ , inputPinmode = mempty+ , readInput = [CLine $ varname <> " = " <> devname <> ".read();"]+ , inputStream = extern varname interpretvalues'+ }+ where+ varname = "arduino_serial_" <> devname <> "_read"+ interpretvalues'+ | null interpretvalues = Nothing+ | otherwise = Just interpretvalues++-- | Value that is read from serial port when there is no input available.+noInput :: Int8+noInput = -1+
+ src/Copilot/Arduino/Library/Serial/XBee.hs view
@@ -0,0 +1,78 @@+-- | XBee serial library for arduino-copilot.+--+-- This module is designed to be imported qualified as XBee++{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}++module Copilot.Arduino.Library.Serial.XBee (+ Baud(..),+ device,+ output,+ char,+ str,+ show,+ showFormatted,+ byte,+ input,+ input',+ noInput,+ ShowableType,+ FormatableType,+ Base(..),+) where++import Copilot.Arduino hiding (show)+import Copilot.Arduino.Library.Serial.Device+import Prelude ()++dev :: SerialDeviceName+dev = SerialDeviceName "XBee"++-- | Configure the XBee device. +--+-- This must be included in your sketch if it uses XBee.+--+-- > XBee.device pin2 pin3 (XBee.Baud 9600)+device+ :: (IsDigitalIOPin rx, IsDigitalIOPin tx)+ => Pin rx -- ^ pin on which to receive serial data+ -> Pin tx -- ^ pin on which to transfer serial data+ -> Baud+ -> Sketch ()+device = deviceD dev++-- | Output to XBee.+--+-- Note that this can only be used once in a Sketch.+--+-- > main = arduino $ doa+-- > XBee.device pin2 pin3 (XBee.Baud 9600)+-- > b <- readfrom pin4+-- > n <- readvoltage a1+-- > XBee.output true+-- > [ Serial.str "pin4:"+-- > , Serial.show b+-- > , Serial.str " a1:"+-- > , Serial.show n+-- > , Serial.char '\n'+-- > ]+output+ :: Stream Bool+ -- ^ This Stream controls when output is sent to the XBee.+ -> [FormatOutput]+ -> Sketch ()+output = outputD dev++-- | Input from the XBee.+--+-- Reads one byte on each iteration of the sketch. When there is no+-- serial input available, reads `noInput`.+--+-- > userinput <- XBee.input+input :: Input Int8+input = inputD dev++-- | The list is used to simulate Xbee input when interpreting the program.+input' :: [Int8] -> Input Int8+input' = input'D dev
src/Copilot/Arduino/Main.hs view
@@ -24,7 +24,7 @@ -- > -- > main = arduino $ do -- > led =: flashing--- > delay =: constant 100+-- > delay =: MilliSeconds (constant 100) -- -- Running this program compiles the `Sketch` into C code using copilot, and -- writes it to a .ino file. That can be built and uploaded to your Arduino