blink1-0.4: System/Hardware/Blink1/Types.hs
{-# LANGUAGE CPP, GeneralizedNewtypeDeriving, FlexibleInstances, TypeSynonymInstances #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module System.Hardware.Blink1.Types
( RGB(..), RGB8
, black
, Delay(..)
, second
, PatternStep(..)
, EEPROMAddr(..)
, serialNumLen
, LED(..)
) where
import Control.Applicative
import Control.Arrow ((***))
import Data.Fixed (HasResolution(..), Centi)
import Data.Monoid (Monoid(..))
import Data.Word (Word8, Word16)
import Numeric (showHex, readHex)
data RGB a = RGB { red, green, blue :: !a }
type RGB8 = RGB Word8
black :: RGB8
black = RGB 0 0 0
instance Functor RGB where
fmap f (RGB r g b) = RGB (f r) (f g) (f b)
instance Applicative RGB where
pure x = RGB x x x
RGB fr fg fb <*> RGB r g b = RGB (fr r) (fg g) (fb b)
instance Num a => Num (RGB a) where
(+) = liftA2 (+)
(-) = liftA2 (-)
(*) = liftA2 (*)
negate = fmap negate
abs = fmap abs
signum = fmap signum
fromInteger = pure . fromInteger
clipAdd :: (Num a, Ord a, Bounded a) => a -> a -> a
clipAdd x y
| z < x = maxBound
| otherwise = z
where z = x + y
-- |like 'Sum' but clips overflowing values at 'maxBound'
instance (Num a, Ord a, Bounded a) => Monoid (RGB a) where
mempty = 0
mappend = liftA2 clipAdd
showHex2 :: Word8 -> ShowS
showHex2 x
| x < 16 = showChar '0' . showHex x
| otherwise = showHex x
-- |uses #RRGGBB format
instance Show RGB8 where
showsPrec _ (RGB r g b) = showChar '#' . showHex2 r . showHex2 g . showHex2 b
instance Read RGB8 where
readsPrec _ ('#':c) = rc2 c ++ rc1 c where
rc1 (r:g:b:s) = rc (0x11*) [r] [g] [b] s
rc1 _ = []
rc2 (r1:r2:g1:g2:b1:b2:s) = rc id [r1,r2] [g1,g2] [b1,b2] s
rc2 _ = []
rc f r g b s = do
(r,"") <- readHex r
(g,"") <- readHex g
(b,"") <- readHex b
return (RGB (f r) (f g) (f b), s)
readsPrec _ _ = []
-- | time is measured in centiseconds
newtype Delay = Delay { delayCentiseconds :: Word16 } deriving (Bounded, Eq, Ord, Enum)
sec :: Num a => a
sec = 100
second :: Delay
second = Delay sec
-- This boiler-plate fixed-point is all possibly over-kill, but is hopefully at least unambiguous, and better than using Centi
instance HasResolution Delay where
resolution _ = sec
-- | operations are based on seconds
instance Num Delay where
Delay x + Delay y = Delay (x + y)
Delay x - Delay y = Delay (x - y)
Delay x * Delay y = Delay (x * y `div` sec) -- XXX: overflow
negate (Delay x) = Delay (negate x)
abs (Delay x) = Delay (abs x)
signum (Delay x) = Delay (signum x * sec)
fromInteger i = Delay (fromInteger i * sec)
instance Real Delay where
toRational (Delay x) = toRational x / sec
instance Fractional Delay where
Delay x / Delay y = Delay (x * sec `div` y) -- XXX: overflow
recip (Delay x) = Delay (sec * sec `div` x)
fromRational r = Delay (floor (r * sec))
instance RealFrac Delay where
properFraction (Delay x) = fromIntegral *** Delay $ x `divMod` sec
truncate (Delay x) = fromIntegral (x `div` sec)
round (Delay x) = truncate (Delay (x + (pred sec `div` 2)))
ceiling (Delay x) = truncate (Delay (x + pred sec))
floor x = truncate x -- unsigned!
instance Show Delay where
showsPrec p d = showsPrec p (realToFrac d :: Centi) . showChar 's'
instance Read Delay where
readsPrec p = map f . readsPrec p where
f (x,'s':s) = (realToFrac x, s)
f (x,'c':'s':s) = (Delay (floor x), s)
f (x,'m':'s':s) = (Delay (floor x `div` 10), s)
f (x,s) = (realToFrac (x ::
#if MIN_VERSION_base(4,4,0)
Centi
#else
Float
#endif
), s)
-- | positions are counted 0-11 on mk1, 0-31 on mk2
newtype PatternStep = PatternStep { patternStep :: Word8 } deriving (Eq, Ord, Enum, Num, Show, Read)
instance Bounded PatternStep where
minBound = PatternStep 0
maxBound = PatternStep 31 -- 11 on mk1
data EEPROMAddr
= EEOSCCAL
| EEBootMode
| EESerialNum Word8
| EEPatternStart
deriving (Eq, Ord)
serialNumLen :: Word8
serialNumLen = 4
instance Enum EEPROMAddr where
fromEnum EEOSCCAL = 0
fromEnum EEBootMode = 1
fromEnum (EESerialNum i)
| i < serialNumLen = 2 + fromIntegral i
| otherwise = error "EEPROMAddr.fromEnum: invalid EESerialNum"
fromEnum EEPatternStart = 6
toEnum 0 = EEOSCCAL
toEnum 1 = EEBootMode
toEnum 6 = EEPatternStart
toEnum x
| x >= 2 && x < 6 = EESerialNum (fromIntegral x-2)
| otherwise = error "EEPROMAddr.toEnum: invalid address"
instance Bounded EEPROMAddr where
minBound = EEOSCCAL
maxBound = EEPatternStart
-- | LEDs are 1-based (0 means "all")
newtype LED = LED { whichLED :: Word8 } deriving (Eq, Ord, Enum, Num, Show, Read)
instance Bounded LED where
minBound = LED 1
maxBound = LED maxBound