arduino-copilot 1.2.0 → 1.3.0
raw patch · 16 files changed
+499/−228 lines, 16 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Copilot.Arduino: PWMDutyCycle :: Behavior Word8 -> PWMDutyCycle
- 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: readfrom :: IsDigitalIOPin t => Pin t -> Input Bool
- Copilot.Arduino: readfrom' :: IsDigitalIOPin t => Pin t -> [Bool] -> Input Bool
- Copilot.Arduino: readvoltage :: IsAnalogInputPin t => Pin t -> Input Voltage
- Copilot.Arduino: readvoltage' :: IsAnalogInputPin t => Pin t -> [Int16] -> Input Voltage
- Copilot.Arduino: type Input t = Sketch (Stream t)
- Copilot.Arduino.Internals: alwaysEvent :: v -> Event v
- 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.Internals: type Input t = Sketch (Stream t)
- Copilot.Arduino.Library.Serial: input :: Input Int8
- Copilot.Arduino.Library.Serial: input' :: [Int8] -> Input Int8
- Copilot.Arduino.Library.Serial: output :: Stream Bool -> [FormatOutput] -> Sketch ()
- Copilot.Arduino.Library.Serial.Device: deviceD :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => SerialDeviceName -> Pin rx -> Pin tx -> Baud -> Sketch ()
- 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: outputD :: SerialDeviceName -> Stream Bool -> [FormatOutput] -> Sketch ()
- Copilot.Arduino.Library.Serial.XBee: input :: Input Int8
- Copilot.Arduino.Library.Serial.XBee: input' :: [Int8] -> Input Int8
- Copilot.Arduino.Library.Serial.XBee: output :: Stream Bool -> [FormatOutput] -> Sketch ()
+ Copilot.Arduino: class Input o t
+ Copilot.Arduino: data TypedBehavior p t
+ Copilot.Arduino: input :: Input o t => o -> Sketch (Behavior t)
+ Copilot.Arduino: input' :: Input o t => o -> [t] -> Sketch (Behavior t)
+ Copilot.Arduino: pwm :: Behavior Word8 -> TypedBehavior 'PWM Word8
+ Copilot.Arduino.Internals: TypedBehavior :: Behavior t -> TypedBehavior p t
+ Copilot.Arduino.Internals: class Input o t
+ Copilot.Arduino.Internals: class IsBehavior behavior
+ Copilot.Arduino.Internals: data TypedBehavior p t
+ Copilot.Arduino.Internals: input' :: Input o t => o -> [t] -> Sketch (Behavior t)
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsAnalogInputPin t => Copilot.Arduino.Internals.Input (Copilot.Arduino.Internals.Pin t) Copilot.Arduino.Internals.Voltage
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsBehavior (Copilot.Arduino.Internals.Behavior v)
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsDigitalIOPin t => Copilot.Arduino.Internals.Input (Copilot.Arduino.Internals.Pin t) GHC.Types.Bool
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsDigitalIOPin t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Internals.Pin t) (Copilot.Arduino.Internals.Event () (Copilot.Language.Stream.Stream GHC.Types.Bool))
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.IsPWMPin t => Copilot.Arduino.Internals.Output (Copilot.Arduino.Internals.Pin t) (Copilot.Arduino.Internals.Event 'Copilot.Arduino.Internals.PWM (Copilot.Language.Stream.Stream GHC.Word.Word8))
+ Copilot.Arduino.Internals: instance Copilot.Arduino.Internals.Output o (Copilot.Arduino.Internals.Event () (Copilot.Language.Stream.Stream v)) => Copilot.Arduino.Internals.Output o (Copilot.Arduino.Internals.Behavior v)
+ Copilot.Arduino.Internals: instance forall k (p :: k) v. Copilot.Arduino.Internals.IsBehavior (Copilot.Arduino.Internals.TypedBehavior p v)
+ Copilot.Arduino.Internals: instance forall k o (p :: k) v. Copilot.Arduino.Internals.Output o (Copilot.Arduino.Internals.Event p (Copilot.Language.Stream.Stream v)) => Copilot.Arduino.Internals.Output o (Copilot.Arduino.Internals.TypedBehavior p v)
+ Copilot.Arduino.Internals: type Voltage = Int16
+ Copilot.Arduino.Library.Serial: data FormatOutput
+ Copilot.Arduino.Library.Serial: data SerialDevice
+ Copilot.Arduino.Library.Serial: device :: SerialDevice
+ Copilot.Arduino.Library.Serial.Device: SerialDevice :: SerialDeviceName -> SerialDevice
+ Copilot.Arduino.Library.Serial.Device: configureD :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => SerialDeviceName -> Pin rx -> Pin tx -> Baud -> Sketch ()
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Internals.Input Copilot.Arduino.Library.Serial.Device.SerialDevice GHC.Int.Int8
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Internals.IsBehavior [Copilot.Arduino.Library.Serial.Device.FormatOutput]
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Internals.Output Copilot.Arduino.Library.Serial.Device.SerialDevice (Copilot.Arduino.Internals.Event () [Copilot.Arduino.Library.Serial.Device.FormatOutput])
+ Copilot.Arduino.Library.Serial.Device: instance Copilot.Arduino.Internals.Output Copilot.Arduino.Library.Serial.Device.SerialDevice [Copilot.Arduino.Library.Serial.Device.FormatOutput]
+ Copilot.Arduino.Library.Serial.Device: newtype SerialDevice
+ Copilot.Arduino.Library.Serial.XBee: configure :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => Pin rx -> Pin tx -> Baud -> Sketch ()
+ Copilot.Arduino.Library.Serial.XBee: data FormatOutput
+ Copilot.Arduino.Library.Serial.XBee: data SerialDevice
- Copilot.Arduino: (@:) :: v -> Behavior Bool -> Event v
+ Copilot.Arduino: (@:) :: IsBehavior behavior => behavior -> Behavior Bool -> BehaviorToEvent behavior
- Copilot.Arduino: data Event v
+ Copilot.Arduino: data Event p v
- Copilot.Arduino.Internals: (@:) :: v -> Behavior Bool -> Event v
+ Copilot.Arduino.Internals: (@:) :: IsBehavior behavior => behavior -> Behavior Bool -> BehaviorToEvent behavior
- Copilot.Arduino.Internals: Event :: v -> Stream Bool -> Event v
+ Copilot.Arduino.Internals: Event :: v -> Stream Bool -> Event p v
- Copilot.Arduino.Internals: data Event v
+ Copilot.Arduino.Internals: data Event p v
- Copilot.Arduino.Internals: mkInput :: InputSource t -> Input t
+ Copilot.Arduino.Internals: mkInput :: InputSource t -> Sketch (Behavior t)
- Copilot.Arduino.Internals: type family SameCapability a b :: Bool
+ Copilot.Arduino.Internals: type family BehaviorToEvent a
- Copilot.Arduino.Library.Serial.XBee: device :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => Pin rx -> Pin tx -> Baud -> Sketch ()
+ Copilot.Arduino.Library.Serial.XBee: device :: SerialDevice
Files
- CHANGELOG +11/−0
- TODO +47/−12
- arduino-copilot.cabal +6/−1
- examples/button/demo.hs +1/−1
- examples/serialport/Makefile +127/−0
- examples/serialport/README +24/−0
- examples/serialport/demo.hs +49/−0
- examples/serialport/pre-build-hook.sh +2/−0
- src/Copilot/Arduino.hs +24/−65
- src/Copilot/Arduino/Internals.hs +92/−32
- src/Copilot/Arduino/Library/Serial.hs +19/−29
- src/Copilot/Arduino/Library/Serial/Device.hs +51/−48
- src/Copilot/Arduino/Library/Serial/XBee.hs +22/−32
- src/Copilot/Arduino/Nano.hs +0/−4
- src/Copilot/Arduino/Uno.hs +0/−4
- test.hs +24/−0
CHANGELOG view
@@ -1,3 +1,14 @@+arduino-copilot (1.3.0) unstable; urgency=medium++ * New polymorphic "input" action that can be used to read digital and+ analog pins, as well as read from serial ports, and replaces+ readfrom, readvoltage, etc.+ * Replaced PWMDutyCycle with pwm.+ * Changed the Serial and XBee modules to work with the =: operator+ and "input".++ -- Joey Hess <id@joeyh.name> Thu, 30 Jan 2020 22:33:20 -0400+ arduino-copilot (1.2.0) unstable; urgency=medium * Bugfix: Doing the same kind of write to two different pins in the
TODO view
@@ -12,29 +12,64 @@ 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.)+ (Note that, if @: makes the first read be skipped, or all of them,+ there would need to be a default value produced. Or, to avoid default+ values, it could force the first read to always occur.) - A polymorphic read operation could work, assuming type inference is- able to usually determine what kind of value is wanted to be read.+ An Input returns a Behavior; with @: it should really return an Event.+ But, that would need boilerplate to get the Stream out to use. - a <- stream a1- pin1 := a+ - foo <- input pin4+ + Event foo _ <- input pin4 @: longer_and_longer+ pin5 =: streamfunc foo - 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!)+ Seems not worth the distinction of Behavior vs Event here.+ +* If @: could somehow be applied to a whole Sketch () action, it would be+ possible to write some interesting combinators based on it, eg: + ifM :: Stream Bool -> Sketch a -> Sketch a -> Sketch a++ schedule :: Stream Int -> [(Int, Sketch a] -> Sketch a++ Although, it may be that these would cause some explosion of the binary+ size, since each trigger in a Sketch's guard would, presumably, turn+ into a C function that duplicates the same code. Perhaps the C compiler+ could notice the common expressions and optimise them?++ Anyway, this would need the Sketch () to contain not a Spec, which I+ think is too opaque, but a data structure that allows adjusting the+ guards of `trigger` and an equivilant guard be added for `extern`.+ (See also, previous item.) And, it pushes things in the direction of+ needing to support multiple calls to Serial.display and wait with + different parameters, and generally never hardcode the name of the C+ function called by a trigger.++ Now, I like the way that @: currently turns a behavior into an event, in+ FRP style. If @: is applied to a whole Sketch, it would stop doing that.+ That feels like a loss. So, perhaps @: stays as-is, and there's a new+ combinator to do the same thing to a Sketch. Internally, they would both+ adjust the guards of triggers. A bit redundant, but seems worth it to+ keep @: FRP style. (Making @: more polymorphic is also a possibility.)++ Another combinator, that I considered but rejected is:++ (|:) :: Sketch a -> Sketch a -> Sketch a ++ The problem with this is that in foo |: bar |: baz, one of the+ three never runs, because both combinators must branch on the same+ Stream Bool.+ * analogReference() Takes one of a set of defines, which vary depending on board, so how to pass that through Copilot? Could write a C switch statement, on an int, and have the boade modules define effectively enums.-* readvoltage gets a raw ADC value; add a stream function to convert that+* Voltage is a raw ADC value; add a stream function to convert that to a floating point actual voltage, given also a stream that contains the reference voltage.-* delayMicroseconds * pulses * random numbers * Wire library+* test.hs is not wired into the cabal file because it depends on runghc+ having the library available
arduino-copilot.cabal view
@@ -1,5 +1,5 @@ Name: arduino-copilot-Version: 1.2.0+Version: 1.3.0 Cabal-Version: >= 1.8 License: BSD3 Maintainer: Joey Hess <id@joeyh.name>@@ -41,6 +41,11 @@ examples/button/README examples/button/demo.hs examples/button/pre-build-hook.sh+ examples/serialport/Makefile+ examples/serialport/README+ examples/serialport/demo.hs+ examples/serialport/pre-build-hook.sh+ test.hs Library GHC-Options: -Wall -fno-warn-tabs
examples/button/demo.hs view
@@ -17,6 +17,6 @@ main :: IO () main = arduino $ do- buttonpressed <- readfrom' pin12 [False, False, False, True, True]+ buttonpressed <- input' pin12 [False, False, False, True, True] led =: buttonpressed || blinking delay =: MilliSeconds (longer_and_longer * 2)
+ examples/serialport/Makefile view
@@ -0,0 +1,127 @@+# Arduino Make file. Refer to https://github.com/sudar/Arduino-Makefile+BOARD_TAG = uno+include /usr/share/arduino/Arduino.mk++# --- leonardo (or pro micro w/leo bootloader)+#BOARD_TAG = leonardo+#MONITOR_PORT = /dev/ttyACM0+#include /usr/share/arduino/Arduino.mk++# --- mega2560 ide 1.0+#BOARD_TAG = mega2560+#ARDUINO_PORT = /dev/ttyACM0+#include /usr/share/arduino/Arduino.mk++# --- mega2560 ide 1.6+#BOARD_TAG = mega+#BOARD_SUB = atmega2560+#MONITOR_PORT = /dev/ttyACM0+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- nano ide 1.0+#BOARD_TAG = nano328+#MONITOR_PORT = /dev/ttyUSB0+#include /usr/share/arduino/Arduino.mk++# --- nano ide 1.6+#BOARD_TAG = nano+#BOARD_SUB = atmega328+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- pro mini+#BOARD_TAG = pro5v328+#MONITOR_PORT = /dev/ttyUSB0+#include /usr/share/arduino/Arduino.mk++# --- sparkfun pro micro+#BOARD_TAG = promicro16+#ALTERNATE_CORE = promicro+#BOARDS_TXT = $(HOME)/arduino/hardware/promicro/boards.txt+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/promicro/bootloaders+#BOOTLOADER_PATH = caterina+#BOOTLOADER_FILE = Caterina-promicro16.hex+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- chipkit+#BOARD_TAG = mega_pic32+#MPIDE_DIR = /where/you/installed/mpide-0023-linux64-20130817-test+#include /usr/share/arduino/chipKIT.mk++# --- pinoccio+#BOARD_TAG = pinoccio256+#ALTERNATE_CORE = pinoccio+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/pinoccio/bootloaders+#BOOTLOADER_PATH = STK500RFR2/release_0.51+#BOOTLOADER_FILE = boot_pinoccio.hex+#CFLAGS_STD = -std=gnu99+#CXXFLAGS_STD = -std=gnu++11+#include /usr/share/arduino/Arduino.mk++# --- fio+#BOARD_TAG = fio+#include /usr/share/arduino/Arduino.mk++# --- atmega-ng ide 1.6+#BOARD_TAG = atmegang+#BOARD_SUB = atmega168+#MONITOR_PORT = /dev/ttyACM0+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- arduino-tiny ide 1.0+#ISP_PROG = usbasp+#BOARD_TAG = attiny85at8+#ALTERNATE_CORE = tiny+#ARDUINO_VAR_PATH = $(HOME)/arduino/hardware/tiny/cores/tiny+#ARDUINO_CORE_PATH = $(HOME)/arduino/hardware/tiny/cores/tiny+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- arduino-tiny ide 1.6+#ISP_PROG = usbasp+#BOARD_TAG = attiny85at8+#ALTERNATE_CORE = tiny+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- damellis attiny ide 1.0+#ISP_PROG = usbasp+#BOARD_TAG = attiny85+#ALTERNATE_CORE = attiny-master+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- damellis attiny ide 1.6+#ISP_PROG = usbasp+#BOARD_TAG = attiny+#BOARD_SUB = attiny85+#ALTERNATE_CORE = attiny+#F_CPU = 16000000L+#ARDUINO_DIR = /where/you/installed/arduino-1.6.5+#include /usr/share/arduino/Arduino.mk++# --- teensy3+#BOARD_TAG = teensy31+#ARDUINO_DIR = /where/you/installed/the/patched/teensy/arduino-1.0.6+#include /usr/share/arduino/Teensy.mk++# --- mighty 1284p+#BOARD_TAG = mighty_opt+#BOARDS_TXT = $(HOME)/arduino/hardware/mighty-1284p/boards.txt+#BOOTLOADER_PARENT = $(HOME)/arduino/hardware/mighty-1284p/bootloaders+#BOOTLOADER_PATH = optiboot+#BOOTLOADER_FILE = optiboot_atmega1284p.hex+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#include /usr/share/arduino/Arduino.mk++# --- atmega328p on breadboard+#BOARD_TAG = atmega328bb+#ISP_PROG = usbasp+#AVRDUDE_OPTS = -v+#BOARDS_TXT = $(HOME)/arduino/hardware/breadboard/boards.txt+#include /usr/share/arduino/Arduino.mk
+ examples/serialport/README view
@@ -0,0 +1,24 @@+This is a demo program using arduino-copilot. ++To build the C code:++ runghc demo.hs++The resulting `.ino` sketch can be loaded into the Arduino IDE and flashed+to an Arduino Uno board using the IDE.++## Arduino-Makefile integration++The `Makefile` and `pre-build-hook.sh` show how to integrate arduino-copilot+with <https://github.com/sudar/Arduino-Makefile>. This automates generating+the C code, compiling that, and flashing it onto an Arduino Uno board,+with a single command++ make upload++Note that you will need to manually build the C code once, as shown above+, since the Arduino-Makefile expects to find a `.ino` file.++The path in the `Makefile` to `Arduino.mk` may need to be adjusted (the+default is the location where on a Debian system, `apt-get install+arduino-mk` will install it).
+ examples/serialport/demo.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE RebindableSyntax #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleContexts #-}++import Copilot.Arduino.Uno+import qualified Copilot.Arduino.Library.Serial as Serial+import qualified Copilot.Arduino.Library.Serial.XBee as XBee+import Control.Monad++-- Should it use the onboard serial port?+useSerial :: Bool+useSerial = True++-- Should it use the XBee wireless serial port?+-- (it can use more than one serial port at the same time.)+useXBee :: Bool+useXBee = True++userIsTyping :: Stream Int8 -> Stream Bool+userIsTyping userinput = userinput /= constant Serial.noInput++getSerial :: Input o Int8 => Bool -> o -> Sketch (Behavior Int8)+getSerial True i = input i+getSerial False _ = return (constant Serial.noInput)++main :: IO ()+main = arduino $ do+ when useSerial $ Serial.baud 9600+ when useXBee $ XBee.configure pin2 pin3 (XBee.Baud 9600)++ -- Measure the voltage of an analog input, and display it to the+ -- serial port. This is a handy demo because with nothing connected+ -- to a0, just moving your hand close to the arduino will probably+ -- cause some noise to be read.+ n <- input a0 :: Sketch (Behavior Voltage)+ let msg = + [ Serial.str "analog input:"+ , Serial.showFormatted n Serial.HEX+ , Serial.char '\n'+ ]+ when useSerial $ Serial.device =: msg+ when useXBee $ XBee.device =: msg++ -- Light the led whenever the user types on the serial port.+ userinput <- getSerial useSerial Serial.device+ userinputxbee <- getSerial useXBee XBee.device+ led =: userIsTyping userinput || userIsTyping userinputxbee++ delay =: MilliSeconds (constant 100)
+ examples/serialport/pre-build-hook.sh view
@@ -0,0 +1,2 @@+#!/bin/sh+exec runghc demo.hs
src/Copilot/Arduino.hs view
@@ -8,11 +8,7 @@ {-# LANGUAGE RebindableSyntax #-} {-# LANGUAGE DataKinds #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}-{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE UndecidableInstances #-} module Copilot.Arduino ( -- * Arduino sketch generation@@ -21,16 +17,15 @@ Pin, -- * Functional reactive programming Behavior,+ TypedBehavior, Event, (@:), -- * Inputs Input,- readfrom,- readfrom',+ input,+ input', pullup, Voltage,- readvoltage,- readvoltage', -- * Outputs -- -- | Only a few common outputs are included in this module.@@ -39,7 +34,7 @@ Output, led, (=:),- PWMDutyCycle(..),+ pwm, delay, MilliSeconds(..), MicroSeconds(..),@@ -109,37 +104,31 @@ instance Output Delay MicroSeconds where Delay =: (MicroSeconds n) = tell [(trigger "delayMicroseconds" true [arg n], mempty)]---- | Reading a Bool from a `Pin`.+ +-- | Use this to read a value from a component of the Arduino. ----- > buttonpressed <- readfrom pin12+-- For example, to read a digital value from pin12 and turn on the +-- led when the pin is high:+--+-- > buttonpressed <- input pin12 -- > led =: buttonpressed-readfrom :: IsDigitalIOPin t => Pin t -> Input Bool-readfrom p = readfrom' p []---- | The list is used as simulated input when interpreting the program.-readfrom' :: IsDigitalIOPin t => Pin t -> [Bool] -> Input Bool-readfrom' (Pin p@(PinId n)) interpretvalues = mkInput $ InputSource- { defineVar = [CLine $ "bool " <> varname <> ";"]- , setupInput = []- , inputPinmode = M.singleton p InputMode- , readInput = [CLine $ varname <> " = digitalRead(" <> show n <> ");"]- , inputStream = extern varname interpretvalues'- }- where- varname = "arduino_digital_pin_input" <> show n- interpretvalues'- | null interpretvalues = Nothing- | otherwise = Just interpretvalues+--+-- Some pins support multiple types of reads, for example pin a0+-- supports a digital read (`Bool`), and an analog read (`Voltage`).+-- In such cases you may need to specify the type of data to read:+--+-- > v <- input a0 :: Sketch (Behavior Voltage)+input :: Input o t => o -> Sketch (Behavior t)+input o = input' o [] --- | Normally when a `Pin` is read with `readfrom`, it is configured+-- | Normally when a digital value is read from a `Pin`, it is configured -- without the internal pullup resistor being enabled. Use this to enable -- the pullup register for all reads from the `Pin`. -- -- Bear in mind that enabling the pullup resistor inverts the value that -- will be read from the pin. ----- > pullup pin3+-- > pullup pin12 pullup :: Pin t -> Sketch () pullup (Pin p) = tell [(return (), f)] where@@ -147,44 +136,14 @@ { pinmodes = M.singleton p (S.singleton InputPullupMode) } --- | Voltage read from an Arduino's ADC. Ranges from 0-1023.-type Voltage = Int16---- | Measuring the voltage of a `Pin`.-readvoltage :: IsAnalogInputPin t => Pin t -> Input Voltage-readvoltage p = readvoltage' p []--readvoltage' :: IsAnalogInputPin t => Pin t -> [Int16] -> Input Voltage-readvoltage' (Pin (PinId n)) interpretvalues = mkInput $ InputSource- { defineVar = [CLine $ "int " <> varname <> ";"]- , setupInput = []- , inputPinmode = mempty- , readInput = [CLine $ varname <> " = analogRead(" <> show n <> ");"]- , inputStream = extern varname interpretvalues'- }- where- varname = "arduino_analog_pin_input" <> show n- interpretvalues'- | null interpretvalues = Nothing- | otherwise = Just interpretvalues- -- | Use this to do PWM output to a pin. ----- > pin3 =: PWMDutyCycle (constant 128)+-- > pin3 =: pwm (constant 128) -- --- Each Word8 from the Stream describes a PWM square wave.+-- Each Word8 of the Behavior describes a PWM square wave. -- 0 is always off and 255 is always on.-data PWMDutyCycle = PWMDutyCycle (Behavior Word8)--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--instance IsPWMPin t => Output (Pin t) PWMDutyCycle where- (=:) o s = o =: alwaysEvent s+pwm :: Behavior Word8 -> TypedBehavior 'PWM Word8+pwm = TypedBehavior -- | The on-board LED. led :: Pin '[ 'DigitalIO ]
src/Copilot/Arduino/Internals.hs view
@@ -30,6 +30,16 @@ -- <https://copilot-language.github.io/> type Behavior t = Stream t +-- | A Behavior with an additional phantom type `p`.+--+-- The Compilot DSL only lets a Stream contain basic C types,+-- a limitation that `Behavior` also has. When more type safely+-- is needed, this can be used.+data TypedBehavior p t = TypedBehavior (Behavior t)++-- | A discrete event, that occurs at particular points in time.+data Event p v = Event v (Stream Bool)+ -- | An Arduino sketch, implemented using Copilot. -- -- It's best to think of the `Sketch` as a description of the state of the@@ -75,10 +85,16 @@ instance Monoid Framework where mempty = Framework mempty mempty mempty mempty --- | A source of a `Stream` of values input from the Arduino.------ Runs in the `Sketch` monad.-type Input t = Sketch (Stream t)+-- | 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 data InputSource t = InputSource { defineVar :: [CLine]@@ -93,7 +109,7 @@ , inputStream :: Stream t } -mkInput :: InputSource t -> Input t+mkInput :: InputSource t -> Sketch (Behavior t) mkInput i = do tell [(return (), f)] return (inputStream i)@@ -159,7 +175,7 @@ -- | 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`+ -- ^ Connect a `Behavior` or `Event` to an `Output` -- -- > led =: blinking --@@ -173,7 +189,8 @@ -- > led =: true -- -- To avoid unncessary work being done, you can use an `Event`- -- instead. Then only new values of the `Event` will be written.+ -- instead. Then the write only happens at the points in time+ -- when the `Event` occurs. -- -- So to make the LED only be turned on in the first iteration, -- and allow it to remain on thereafter without doing extra work:@@ -183,25 +200,36 @@ -- 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 () (Stream v)) => Output o (Behavior v) where+ (=:) o b = o =: te+ where+ te :: Event () (Stream v)+ te = Event b true -instance Output o (Event (Behavior v)) => Output o (Behavior v) where- (=:) o s = o =: alwaysEvent s+instance Output o (Event p (Stream v)) => Output o (TypedBehavior p v) where+ (=:) o (TypedBehavior b) = o =: te+ where+ te :: Event p (Stream v)+ te = Event b true -alwaysEvent :: v -> Event v-alwaysEvent s = Event s true+-- | This type family is open, so it can be extended when adding other data+-- types to the IsBehavior class.+type family BehaviorToEvent a+type instance BehaviorToEvent (Behavior v) = Event () (Stream v)+type instance BehaviorToEvent (TypedBehavior p v) = Event p (Stream v) --- | 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+class IsBehavior behavior where+ -- | Generate an event, from some type of behavior,+ -- that only occurs when the `Behavior` Bool is True.+ (@:) :: behavior -> Behavior Bool -> BehaviorToEvent behavior -instance IsDigitalIOPin t => Output (Pin t) (Event (Behavior Bool)) where+instance IsBehavior (Behavior v) where+ b @: c = Event b c++instance IsBehavior (TypedBehavior p v) where+ (@:) (TypedBehavior b) c = Event b c++instance IsDigitalIOPin t => Output (Pin t) (Event () (Stream Bool)) where (Pin p@(PinId n)) =: (Event b c) = tell [(go, f)] where go = trigger triggername c [arg (constant n), arg b]@@ -209,13 +237,45 @@ 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+instance IsPWMPin t => Output (Pin t) (Event 'PWM (Stream Word8)) where+ (Pin (PinId n)) =: (Event 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++class Input o t where+ -- | The list is input to use when simulating the Sketch.+ input' :: o -> [t] -> Sketch (Behavior t)++instance IsDigitalIOPin t => Input (Pin t) Bool where+ input' (Pin p@(PinId n)) interpretvalues = mkInput $ InputSource+ { defineVar = [CLine $ "bool " <> varname <> ";"]+ , setupInput = []+ , inputPinmode = M.singleton p InputMode+ , readInput = [CLine $ varname <> " = digitalRead(" <> show n <> ");"]+ , inputStream = extern varname interpretvalues'+ }+ where+ varname = "arduino_digital_pin_input" <> show n+ interpretvalues'+ | null interpretvalues = Nothing+ | otherwise = Just interpretvalues++-- | Voltage read from an Arduino's ADC. Ranges from 0-1023.+type Voltage = Int16++instance IsAnalogInputPin t => Input (Pin t) Voltage where+ input' (Pin (PinId n)) interpretvalues = mkInput $ InputSource+ { defineVar = [CLine $ "int " <> varname <> ";"]+ , setupInput = []+ , inputPinmode = mempty+ , readInput = [CLine $ varname <> " = analogRead(" <> show n <> ");"]+ , inputStream = extern varname interpretvalues'+ }+ where+ varname = "arduino_analog_pin_input" <> show n+ interpretvalues'+ | null interpretvalues = Nothing+ | otherwise = Just interpretvalues+
src/Copilot/Arduino/Library/Serial.hs view
@@ -7,15 +7,15 @@ module Copilot.Arduino.Library.Serial ( baud,- output,+ device, char, str, show, showFormatted, byte,- input,- input', noInput,+ SerialDevice,+ FormatOutput, ShowableType, FormatableType, Base(..),@@ -34,37 +34,27 @@ baud :: Int -> Sketch () baud = baudD dev --- | Output to the serial port.+-- | Use this to communicate with the serial port, both input and output. ----- Note that this can only be used once in a Sketch.+-- To output to the serial port, simply connect this to a [`FormatOutput`]+-- that describes the serial output. Note that you can only do this once+-- in a Sketch. ----- > main = arduino $ do+-- > main = arduino $ doa -- > Serial.baud 9600--- > b <- readfrom pin3--- > n <- readvoltage a1--- > Serial.output true--- > [ Serial.str "pin3:"+-- > b <- input pin4+-- > Serial.device =:+-- > [ 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 serial port.- -> [FormatOutput]- -> Sketch ()-output = outputD dev---- | Input from the serial port. ----- Reads one byte on each iteration of the sketch. When there is no--- serial input available, reads `noInput`.+-- To input from the serial port, use this with `input`. ----- > userinput <- Serial.input-input :: Input Int8-input = inputD dev---- | The list is used to simulate serial input when interpreting the program.-input' :: [Int8] -> Input Int8-input' = input'D dev+-- > userinput <- input Serial.device+--+-- The resulting `Behavior Int8` will be updated on each iteration+-- of the sketch. When there is no new serial input available, it will+-- contain `noInput`.+device :: SerialDevice+device = SerialDevice dev
src/Copilot/Arduino/Library/Serial/Device.hs view
@@ -25,7 +25,7 @@ import Data.Proxy import qualified Prelude --- | Eg "Serial" or "Serial2"+-- | Eg \"Serial\" or \"Serial2\" newtype SerialDeviceName = SerialDeviceName String baudD :: SerialDeviceName -> Int -> Sketch ()@@ -38,14 +38,14 @@ newtype Baud = Baud Int deriving (Show, Eq) -deviceD+configureD :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => SerialDeviceName -> Pin rx -> Pin tx -> Baud -> Sketch ()-deviceD d@(SerialDeviceName devname) (Pin (PinId rxpin)) (Pin (PinId txpin)) (Baud n) = do+configureD d@(SerialDeviceName devname) (Pin (PinId rxpin)) (Pin (PinId txpin)) (Baud n) = do baudD d n tell [(return (), f)] where@@ -62,32 +62,58 @@ ] } -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 }+newtype SerialDevice = SerialDevice SerialDeviceName - triggername = "arduino_serial_" <> devname <> "_output"+instance Input SerialDevice Int8 where+ input' (SerialDevice (SerialDeviceName devname)) interpretvalues =+ mkInput s+ where+ s = InputSource+ { defineVar = [CLine $ "int " <> varname <> ";"]+ , setupInput = []+ , inputPinmode = mempty+ , readInput = [CLine $ varname <> " = " <> devname <> ".read();"]+ , inputStream = extern varname interpretvalues'+ }+ varname = "arduino_serial_" <> devname <> "_read"+ interpretvalues'+ | null interpretvalues = Nothing+ | otherwise = Just interpretvalues - printer = concat- [ [CLine $ "void " <> triggername <> "("- <> intercalate ", " arglist <> ") {"]- , map (\(fmt, n) -> CLine (" " <> fromCLine (fmt n)))- (zip (map (\fo -> formatCLine fo sdn) l) argnames)- , [CLine "}"]- ]+-- | 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) =+ tell [(go, f)]+ where+ go = trigger triggername c (mapMaybe formatArg l)+ f = mempty { defines = printer }++ triggername = "arduino_serial_" <> devname <> "_output" - 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+ 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 +instance IsBehavior [FormatOutput] where+ (@:) = Event++type instance BehaviorToEvent [FormatOutput] = Event () [FormatOutput]+ data FormatOutput = FormatOutput { formatArg :: Maybe Arg , formatCType :: Maybe String@@ -183,26 +209,3 @@ 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
@@ -7,16 +7,16 @@ module Copilot.Arduino.Library.Serial.XBee ( Baud(..),+ configure, device,- output, char, str, show, showFormatted, byte,- input,- input', noInput,+ SerialDevice,+ FormatOutput, ShowableType, FormatableType, Base(..),@@ -33,46 +33,36 @@ -- -- This must be included in your sketch if it uses XBee. ----- > XBee.device pin2 pin3 (XBee.Baud 9600)-device+-- > XBee.configure pin2 pin3 (XBee.Baud 9600)+configure :: (IsDigitalIOPin rx, IsDigitalIOPin tx) => Pin rx -- ^ pin on which to receive serial data- -> Pin tx -- ^ pin on which to transfer serial data+ -> Pin tx -- ^ pin on which to send serial data -> Baud -> Sketch ()-device = deviceD dev+configure = configureD dev --- | Output to XBee.+-- | Use this to communicate with the XBee, both input and output. ----- Note that this can only be used once in a Sketch.+-- To output to the XBee, simply connect this to a [`FormatOutput`]+-- that describes the serial output. Note that you can only do this once+-- in a Sketch. -- -- > main = arduino $ doa--- > XBee.device pin2 pin3 (XBee.Baud 9600)--- > b <- readfrom pin4--- > n <- readvoltage a1--- > XBee.output true+-- > XBee.configure pin2 pin3 (XBee.Baud 9600)+-- > b <- input pin4+-- > XBee.device =: -- > [ 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`.+-- To input from the XBee, use this with `input`. ----- > 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+-- > userinput <- input XBee.device+--+-- The resulting `Behavior Int8` will be updated on each iteration+-- of the sketch. When there is no new serial input available, it will+-- contain `noInput`.+device :: SerialDevice+device = SerialDevice dev
src/Copilot/Arduino/Nano.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE DataKinds #-} module Copilot.Arduino.Nano (- -- * General Arduino programming infrastructure module Copilot.Arduino -- * Pins , pin2@@ -26,9 +25,6 @@ , a5 , a6 , a7- -- * UART- -- Not currently supported!- --, uart ) where import Copilot.Arduino
src/Copilot/Arduino/Uno.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE DataKinds #-} module Copilot.Arduino.Uno (- -- * General Arduino programming infrastructure module Copilot.Arduino -- * Pins , pin2@@ -24,9 +23,6 @@ , a3 , a4 , a5- -- * UART- -- Not currently supported!- --, uart ) where import Copilot.Arduino
+ test.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE LambdaCase #-}++import Control.Monad+import System.FilePath+import System.Directory+import System.Process+import System.Exit++main = do+ examples <- filterM doesFileExist+ =<< (map (</> "demo.hs") . map ("examples" </>))+ <$> getDirectoryContents "examples"+ if null examples+ then error "did not find any examples to test"+ else mapM_ shouldCompile examples++shouldCompile :: FilePath -> IO ()+shouldCompile f = do+ let p = (proc "runghc" ["-Wall", "-fno-warn-tabs", takeFileName f])+ { cwd = Just (takeDirectory f) }+ readCreateProcessWithExitCode p "" >>= \case+ (ExitSuccess, "", "") -> return ()+ v -> error $ "runghc " ++ f +++ " produced unexpected result: " ++ show v