packages feed

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