packages feed

arduino-copilot-1.0.0: src/Copilot/Arduino/Internals.hs

-- | You should not need to import this module unless you're adding support
-- for a new model of Arduino, or an Arduino library.

{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE FlexibleInstances #-}

module Copilot.Arduino.Internals where

import Language.Copilot
import Control.Monad.Writer

-- | 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.
newtype Sketch t = Sketch (Writer [(Spec, Framework)] t)
	deriving (Monad, Applicative, Functor, MonadWriter [(Spec, Framework)])

instance Monoid (Sketch ()) where
	mempty = Sketch (return ())

instance Semigroup (Sketch t) where
	(Sketch a) <> (Sketch b) = Sketch (a >> b)

-- | The framework of an Arduino sketch.
data Framework = Framework
	{ defines :: [CFragment]
	-- ^ Things that come before the C code generated by Copilot.
	, setups :: [CFragment]
	-- ^ Things to run in `setup`.
	, loops :: [CFragment]
	-- ^ Things to run in `loop`.
	}

-- | A fragment of C code.
type CFragment = String

instance Semigroup Framework where
	a <> b = Framework
		{ defines = defines a <> defines b
		, setups = setups a <> setups b
		, loops = loops a  <> loops b
		}

instance Monoid Framework where
	mempty = Framework 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 :: [CFragment]
	-- ^ How to set up the output.
	, outputCond :: Stream Bool
	, outputBehavior :: Stream Bool -> Behavior t
	}

instance ToFramework (Output t) where
	toFramework o = Framework
		{ defines = mempty
		, setups = setupOutput o
		, loops = mempty
		}

-- | A source of a `Stream` of values input from the Arduino.
--
-- Runs in the `Sketch` monad.
type Input t = Sketch (Stream t)

data InputSource t = InputSource
	{ defineVar :: [CFragment]
	-- ^ Added to the `Framework`'s `defines`, this typically
	-- defines a C variable.
	, setupInput :: [CFragment]
	-- ^ How to set up the input.
	, readInput :: [CFragment]
	-- ^ How to read a value from the input, this typically
	-- reads a value into a C variable.
	, inputStream :: Stream t
	}

instance ToFramework (InputSource t) where
	toFramework i = Framework
		{ defines = defineVar i
		, setups = setupInput i
		, loops = readInput i
		}

mkInput :: InputSource t -> Input t
mkInput i = do
	tell [(return (), toFramework i)]
	return (inputStream i)

-- | A GPIO pin
--
-- For definitions of GPIO pins like `Copilot.Arduino.Uno.pin12`, 
-- load a module such as Copilot.Arduino.Uno, which provides the pins of a
-- particular board.
newtype GPIO = GPIO Int16

-- FIXME should be a newtype, but how to make a stream of a newtype?
type MicroSeconds = Int16

-- | Makes an arduino sketch, using a Framework, and a list of lines of C
-- code generated by Copilot.
sketchFramework :: Framework -> [String] -> [CFragment]
sketchFramework f ccode = concat
	[
		[ "/* automatically generated, do not edit */"
		, blank
		, "#include <stdbool.h>"
		, "#include <stdint.h>"
		, blank
		]
	, map statement (defines f)
	, [blank]
	, ccode
	, [blank]
	,
		[ "void setup()"
		]
	, codeblock $ map statement (setups f)
	, [blank]
	,
		[ "void loop()"
		]
	, codeblock $ map statement $ (loops f) <>
		[ "step()"
		]
	]
  where
	blank = ""
	indent l = "  " <> l
	statement d = d <> ";"
	codeblock l = ["{"] <> map indent l <> ["}"]