diff --git a/CHANGELOG b/CHANGELOG
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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.
diff --git a/TODO b/TODO
--- a/TODO
+++ b/TODO
@@ -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
diff --git a/arduino-copilot.cabal b/arduino-copilot.cabal
--- a/arduino-copilot.cabal
+++ b/arduino-copilot.cabal
@@ -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:
diff --git a/examples/blink/demo.hs b/examples/blink/demo.hs
--- a/examples/blink/demo.hs
+++ b/examples/blink/demo.hs
@@ -5,4 +5,4 @@
 main :: IO ()
 main = arduino $ do
 	led =: blinking
-	delay =: constant 100
+	delay =: MilliSeconds (constant 100)
diff --git a/examples/button/demo.hs b/examples/button/demo.hs
--- a/examples/button/demo.hs
+++ b/examples/button/demo.hs
@@ -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)
diff --git a/src/Copilot/Arduino.hs b/src/Copilot/Arduino.hs
--- a/src/Copilot/Arduino.hs
+++ b/src/Copilot/Arduino.hs
@@ -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 
diff --git a/src/Copilot/Arduino/Internals.hs b/src/Copilot/Arduino/Internals.hs
--- a/src/Copilot/Arduino/Internals.hs
+++ b/src/Copilot/Arduino/Internals.hs
@@ -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
diff --git a/src/Copilot/Arduino/Library/Serial.hs b/src/Copilot/Arduino/Library/Serial.hs
--- a/src/Copilot/Arduino/Library/Serial.hs
+++ b/src/Copilot/Arduino/Library/Serial.hs
@@ -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
diff --git a/src/Copilot/Arduino/Library/Serial/Device.hs b/src/Copilot/Arduino/Library/Serial/Device.hs
new file mode 100644
--- /dev/null
+++ b/src/Copilot/Arduino/Library/Serial/Device.hs
@@ -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
+
diff --git a/src/Copilot/Arduino/Library/Serial/XBee.hs b/src/Copilot/Arduino/Library/Serial/XBee.hs
new file mode 100644
--- /dev/null
+++ b/src/Copilot/Arduino/Library/Serial/XBee.hs
@@ -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
diff --git a/src/Copilot/Arduino/Main.hs b/src/Copilot/Arduino/Main.hs
--- a/src/Copilot/Arduino/Main.hs
+++ b/src/Copilot/Arduino/Main.hs
@@ -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
