diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,15 @@
+# Changelog for `wled-json`
+
+Hopefully all notable changes to this project will be documented in this file.
+
+The project adheres to the [Haskell Package Versioning Policy](https://pvp.haskell.org/).
+
+## Unreleased
+
+## 0.0.1.0 - Initial release - 2024-12-02
+
+- Core Functionality:
+  - Basic connection to WLED devices
+  - Setting color, brightness, etc.
+- Octolamp Support:
+  - Initial support for Octolamp-specific features (provide the French flag)
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2024 Andreas Ländle
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1.  Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+
+2.  Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation
+    and/or other materials provided with the distribution.
+
+3.  Neither the name of the copyright holder nor the names of its contributors
+    may be used to endorse or promote products derived from this software
+    without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,69 @@
+# wled-json ✨
+
+Haskell bindings for controlling WLED devices, with a focus on Octolamps
+
+<p align="center">
+  <img src="./images/octolamp.jpg" alt="Octolamp showing french flag" width="50%"/>
+</p>
+
+## Introduction 💡
+
+This Haskell library provides a convenient interface for interacting with WLED devices, particularly those based on the popular Octolamp design. It leverages the WLED API (https://kno.wled.ge/) to allow you to control various aspects of your LED lights, including:
+
+- **Color:** Set the color of your lights using various color spaces (RGB, HSV, etc.)
+- **Brightness:** Adjust the overall brightness of your lights
+
+## Installation ⚙️
+
+```bash
+# Using cabal
+cabal add wled-json
+
+# Using stack
+stack add wled-json
+```
+
+## Usage 📲
+
+```haskell
+import           Control.Concurrent
+import           WLED.Device
+import           WLED.Octocat.Flags (france)
+import           WLED.Types
+
+main :: IO ()
+main = do
+    -- Connect to a WLED device
+    lampState <- getLampState wledUrl
+    case lampState of
+        Left errMsg -> putStrLn errMsg
+        Right initialState -> do
+            -- Display the French flag on an Octolamp
+            _ <- setLampState wledUrl france
+
+            -- Just sleep one second
+            threadDelay 1000000
+            
+            -- Restore the initial state
+            Right currentState <- getLampState wledUrl
+            _ <- setLampState wledUrl (diff currentState initialState)
+            pure ()
+  where
+    wledUrl = "http://192.168.178.34"
+
+```
+
+## Features 👍
+
+- Comprehensive WLED API coverage
+- Type-safe interactions
+- Easy-to-use API
+- Specific support for Octolamps
+
+## Contributing 🤝
+
+Contributions are welcome! Please feel free to open an issue or submit a pull request.
+
+## License ✌️
+
+[BSD-3-Clause](./LICENSE)
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import           Distribution.Simple
+main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
new file mode 100644
--- /dev/null
+++ b/app/Main.hs
@@ -0,0 +1,45 @@
+{-# LANGUAGE Arrows                   #-}
+{-# LANGUAGE DataKinds                #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+
+module Main (main) where
+
+import           Control.Monad      (void)
+import           Data.Kind          (Type)
+import           FRP.Rhine
+import           GHC.TypeLits       (Nat)
+import           WLED.Device
+import           WLED.Octocat.Flags (france)
+import           WLED.Types
+
+waitForEnter :: ClSF (ExceptT () IO) StdinClock () ()
+waitForEnter = arrMCl throwE
+
+type TimeClock :: Nat -> Type
+type TimeClock ms = LiftClock IO (ExceptT ()) (Millisecond ms)
+
+sinusWave :: (Int -> ExceptT () IO ()) -> ClSF (ExceptT () IO) (TimeClock 100) () ()
+sinusWave signalHandler = sinceStart >>> proc time -> do
+    arrMCl (signalHandler . scaledSinusWave 0 255 0.1) -< time
+    returnA -< ()
+  where
+    timeToSinusWave :: Double -> Double -> Double
+    timeToSinusWave frequency t = sin (2 * pi * frequency * t)
+    scaledSinusWave :: Int -> Int -> Double -> Double -> Int
+    scaledSinusWave a b frequency time = round $ fromIntegral (b - a) / 2 * timeToSinusWave frequency time + fromIntegral (a + b) / 2
+
+main :: IO ()
+main = do
+    lampState <- getLampState wledUrl
+    case lampState of
+        Left errMsg -> putStrLn errMsg
+        Right initialState -> do
+            _ <- setLampState wledUrl france
+            void $ runExceptT $ flow $ waitForEnter @@ StdinClock |@| sinusWave (\bri -> liftIO $ void $ setLampState wledUrl (mempty :: StatePatch) { stateBri = Just bri }) @@ liftClock waitClock
+            Right currentState <- getLampState wledUrl
+            _ <- setLampState wledUrl (diff currentState initialState)
+            -- validate that initial state is restored
+            Right restoredState <- getLampState wledUrl
+            putStrLn $ "Initial state is restored: " <> show (initialState == restoredState)
+  where
+    wledUrl = "http://192.168.178.34"
diff --git a/src/WLED/Device.hs b/src/WLED/Device.hs
new file mode 100644
--- /dev/null
+++ b/src/WLED/Device.hs
@@ -0,0 +1,57 @@
+{-# LANGUAGE OverloadedStrings   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+{-|
+Module      : WLED.Device
+Copyright   : (c) Andreas Ländle, 2024
+License     : BSD-3
+Stability   : experimental
+
+Interacting with the WLED device.
+-}
+
+module WLED.Device
+    ( getLampState,
+      setLampState
+    ) where
+
+import           Data.Aeson                  (eitherDecodeStrict, encode)
+import           Data.ByteString             (ByteString, toStrict)
+import           Network.HTTP.Client.Conduit (Request (method), path)
+import           Network.HTTP.Simple         (getResponseBody, httpBS, parseRequest, setRequestBodyJSON)
+import           WLED.Types                  (StateComplete, StatePatch)
+
+
+{- |
+Retrieves the current lamp state.
+
+==== __Example__
+
+>>> getLampState "http://192.168.178.34"
+Right (State {stateOn = True, stateBri = 128, stateTransition = 7, statePs = -1, statePl = -1, stateNl = Nightlight {nightlightOn = False, nightlightDur = 60, nightlightMode = 1, nightlightTbri = 0, nightlightRem = -1}, stateLor = 0, stateMainseg = 0, stateSeg = [Segment {segmentId = 0, segmentStart = 0, segmentStop = 101, segmentLen = 101, segmentGrp = 1, segmentSpc = 0, segmentOf = 0, segmentOn = True, segmentFrz = False, segmentBri = 255, segmentCct = 127, segmentSet = 0, segmentCol = [[255,160,0],[0,0,0],[0,0,0]], segmentFx = 0, segmentSx = 128, segmentIx = 128, segmentPal = 0, segmentC1 = 128, segmentC2 = 128, segmentC3 = 16, segmentSel = True, segmentRev = False, segmentMi = False, segmentO1 = False, segmentO2 = False, segmentO3 = False, segmentSi = 0, segmentM12 = 0}]})
+-}
+getLampState :: String -> IO (Either String StateComplete)
+getLampState wledUrl = do
+    req <- parseRequest wledUrl
+    res <- httpBS req { path = "json/state" }
+    pure (eitherDecodeStrict $ getResponseBody res)
+
+{- |
+Alters the current lamp state.
+
+==== __Examples__
+
+>>> setLampState "http://192.168.178.34" $ (mempty :: StatePatch) { stateBri = Just 255 } <> (mempty :: StatePatch) { stateOn = Nothing }
+("{\"bri\":255}","{\"success\":true}")
+
+>>> setLampState "http://192.168.178.34" france
+("{\"seg\":[{\"start\":0,\"stop\":5,\"col\":[[255,255,255]]},{\"start\":5,\"stop\":16,\"col\":[[255,0,0]]},{\"start\":16,\"stop\":23,\"col\":[[255,255,255]]},{\"start\":23,\"stop\":34,\"col\":[[0,0,255]]},{\"start\":34,\"stop\":41,\"col\":[[255,255,255]]},{\"start\":41,\"stop\":52,\"col\":[[0,0,255]]},{\"start\":52,\"stop\":57,\"col\":[[255,255,255]]},{\"start\":57,\"stop\":68,\"col\":[[255,0,0]]},{\"start\":68,\"stop\":101,\"col\":[[255,255,255]]}]}","{\"success\":true}")
+-}
+setLampState :: String -> StatePatch -> IO (ByteString, ByteString)
+setLampState wledUrl patch =
+    let body = encode patch
+    in do
+      req <- parseRequest wledUrl
+      res <- httpBS $ setRequestBodyJSON patch req { method = "POST", path = "json/state" }
+      pure (toStrict body, getResponseBody res)
+
diff --git a/src/WLED/Octocat/Flags.hs b/src/WLED/Octocat/Flags.hs
new file mode 100644
--- /dev/null
+++ b/src/WLED/Octocat/Flags.hs
@@ -0,0 +1,26 @@
+
+{-|
+Module      : WLED.Octocat.Flags
+Copyright   : (c) Andreas Ländle, 2024
+License     : BSD-3
+Stability   : experimental
+
+Octolamp specific feature to display flags.
+-}
+
+module WLED.Octocat.Flags(france) where
+
+import           WLED.Types
+
+-- | The French flag.
+france :: StatePatch
+france = (mempty :: StatePatch) { stateSeg = Just [ seg 0 5 white, seg 5 16 red, seg 16 23 white, seg 23 34 blue, seg 34 41 white, seg 41 52 blue, seg 52 57 white, seg 57 68 red, seg 68 101 white ]} where
+    red :: [Int]
+    red = [255,0,0]
+    white :: [Int]
+    white = [255,255,255]
+    blue :: [Int]
+    blue = [0,0,255]
+
+seg :: Int -> Int -> [Int] -> SegmentPatch
+seg start stop color = (mempty :: SegmentPatch) { segmentStart = Just start, segmentStop = Just stop, segmentCol = Just [color], segmentFx = Just 0 }
diff --git a/src/WLED/Types.hs b/src/WLED/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/WLED/Types.hs
@@ -0,0 +1,243 @@
+{-# LANGUAGE CPP                      #-}
+{-# LANGUAGE DataKinds                #-}
+{-# LANGUAGE DeriveGeneric            #-}
+{-# LANGUAGE DerivingVia              #-}
+{-# LANGUAGE FlexibleInstances        #-}
+{-# LANGUAGE StandaloneDeriving       #-}
+{-# LANGUAGE StandaloneKindSignatures #-}
+{-# LANGUAGE TypeApplications         #-}
+{-# LANGUAGE UndecidableInstances     #-}
+
+
+{-|
+Module      : WLED.Device
+Copyright   : (c) Andreas Ländle, 2024
+License     : BSD-3
+Stability   : experimental
+
+Types representing states and state changes of a WLED device.
+-}
+
+module WLED.Types (State (..), Nightlight (..), Segment (..), StateComplete, StatePatch, NightlightComplete, NightlightPatch, SegmentComplete, SegmentPatch, append, diff) where
+
+import           Barbies.Bare
+import           Control.Applicative      (Alternative ((<|>)), empty)
+import qualified Data.Aeson               as A
+import           Data.Char                (toLower)
+import           Data.Functor.Barbie
+import           Data.Functor.Identity    (Identity (..))
+import           Data.Functor.Transformer
+import           Data.Kind                (Type)
+import           Deriving.Aeson
+
+#if __GLASGOW_HASKELL__ > 906
+import           Data.List                ((!?))
+#endif
+
+-- | State data type.
+type State :: Type -> (Type -> Type)-> (Type -> Type) -> Type
+data State t f f' = State
+    { stateOn         :: Wear t f Bool
+    , stateBri        :: Wear t f Int
+    , stateTransition :: Wear t f Int
+    , statePs         :: Wear t f Int
+    , statePl         :: Wear t f Int
+    , stateNl         :: Wear t f (Nightlight t f')
+--    , udpn :: Wear t f UdpNetwork
+    , stateLor        :: Wear t f Int
+    , stateMainseg    :: Wear t f Int
+    , stateSeg        :: Wear t f [Segment t f']
+    } deriving stock (Generic) --, ConstraintsB, FunctorB, ApplicativeB)
+
+instance Functor f => FunctorB (State Covered f)
+instance FunctorB (State Bare f)
+
+instance FunctorT (State Covered)
+instance ApplicativeT (State Covered)
+
+deriving stock instance Show (State Bare f f')
+deriving stock instance Eq (State Bare f f')
+deriving stock instance (Show (f Bool), Show (f Int), Show (f (Nightlight Covered f')), Show (f [Segment Covered f'])) => Show (State Covered f f')
+deriving stock instance (Eq (f Bool), Eq (f Int), Eq (f (Nightlight Covered f')), Eq (f [Segment Covered f'])) => Eq (State Covered f f')
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "state", ToLower]] (State Bare f f') instance A.FromJSON (State Bare f f')
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "state", ToLower]] (State Bare f f') instance A.ToJSON (State Bare f f')
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "state", ToLower]] (State Covered f f') instance (FromJSON (f Bool), FromJSON (f Int), FromJSON (f (Nightlight Covered f')), FromJSON (f [Segment Covered f'])) => A.FromJSON (State Covered f f')
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "state", ToLower]] (State Covered f f') instance (ToJSON (f Bool), ToJSON (f Int), ToJSON (f (Nightlight Covered f')), ToJSON (f [Segment Covered f'])) => A.ToJSON (State Covered f f')
+
+instance Semigroup StatePatch where
+  (State aOn aBri aTransition aPs aPl aNl aLor aMainseg aSeg) <> (State bOn bBri bTransition bPs bPl bNl bLor bMainseg bSeg) = State (aOn <|> bOn) (aBri <|> bBri) (aTransition <|> bTransition) (aPs <|> bPs) (aPl <|> bPl) (aNl <||> bNl) (aLor <|> bLor) (aMainseg <|> bMainseg) (aSeg <|||> bSeg)
+    where
+      (<||>) :: Semigroup a => Maybe a -> Maybe a -> Maybe a
+      (<||>) (Just a) (Just b) = Just $ a <> b
+      (<||>) Nothing r         = r
+      (<||>) l       _         = l
+      (<|||>) :: Semigroup a => Maybe [a] -> Maybe [a] -> Maybe [a]
+      (<|||>) (Just a) (Just b) = Just $ zipWith (<>) a b
+      (<|||>) Nothing r         = r
+      (<|||>) l       _         = l
+
+instance Monoid StatePatch where
+  mempty = tpure empty
+
+-- | Nightlight data type.
+type Nightlight :: Type -> (Type -> Type) -> Type
+data Nightlight t f = Nightlight
+    { nightlightOn   :: Wear t f Bool
+    , nightlightDur  :: Wear t f Int
+    , nightlightMode ::Wear t f  Int
+    , nightlightTbri ::Wear t f  Int
+    , nightlightRem  ::Wear t f  Int
+    } deriving stock (Generic)
+
+instance ConstraintsB (Nightlight Covered)
+instance FunctorB (Nightlight Covered)
+instance ApplicativeB (Nightlight Covered)
+
+instance ConstraintsB (Nightlight Bare)
+instance FunctorB (Nightlight Bare)
+instance BareB Nightlight
+
+deriving stock instance (AllBF Show f (Nightlight Bare)) => Show (Nightlight Bare f)
+deriving stock instance (AllBF Eq f (Nightlight Bare)) => Eq (Nightlight Bare f)
+deriving stock instance (AllBF Show f (Nightlight Covered)) => Show (Nightlight Covered f)
+deriving stock instance (AllBF Eq f (Nightlight Covered)) => Eq (Nightlight Covered f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "nightlight", ToLower]] (Nightlight Bare f) instance (AllBF A.FromJSON f (Nightlight Bare)) => A.FromJSON (Nightlight Bare f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "nightlight", ToLower]] (Nightlight Bare f) instance (AllBF A.ToJSON f (Nightlight Bare)) => A.ToJSON (Nightlight Bare f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "nightlight", ToLower]] (Nightlight Covered f) instance (AllBF A.FromJSON f (Nightlight Covered)) => A.FromJSON (Nightlight Covered f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "nightlight", ToLower]] (Nightlight Covered f) instance (AllBF A.ToJSON f (Nightlight Covered)) => A.ToJSON (Nightlight Covered f)
+
+instance (Alternative f) => Semigroup (Nightlight Covered f) where
+  (<>) = bzipWith (<|>)
+
+instance (Alternative f) => Monoid (Nightlight Covered f) where
+  mempty = bpure empty
+
+-- | Segment data type.
+type Segment :: Type -> (Type -> Type) -> Type
+data Segment t f = Segment
+    { segmentId    :: Wear t f Int
+    , segmentStart :: Wear t f Int
+    , segmentStop  :: Wear t f Int
+    , segmentLen   :: Wear t f Int
+    , segmentGrp   :: Wear t f Int
+    , segmentSpc   :: Wear t f Int
+    , segmentOf    :: Wear t f Int
+    , segmentOn    :: Wear t f Bool
+    , segmentFrz   :: Wear t f Bool
+    , segmentBri   :: Wear t f Int
+    , segmentCct   :: Wear t f Int
+    , segmentSet   :: Wear t f Int
+    , segmentCol   :: Wear t f [[Int]]
+    , segmentFx    :: Wear t f Int
+    , segmentSx    :: Wear t f Int
+    , segmentIx    :: Wear t f Int
+    , segmentPal   :: Wear t f Int
+    , segmentC1    :: Wear t f Int
+    , segmentC2    :: Wear t f Int
+    , segmentC3    :: Wear t f Int
+    , segmentSel   :: Wear t f Bool
+    , segmentRev   :: Wear t f Bool
+    , segmentMi    :: Wear t f Bool
+    , segmentO1    :: Wear t f Bool
+    , segmentO2    :: Wear t f Bool
+    , segmentO3    :: Wear t f Bool
+    , segmentSi    :: Wear t f Int
+    , segmentM12   :: Wear t f Int
+    } deriving stock (Generic)
+
+instance ConstraintsB (Segment Covered)
+instance FunctorB (Segment Covered)
+instance ApplicativeB (Segment Covered)
+
+instance ConstraintsB (Segment Bare)
+instance FunctorB (Segment Bare)
+instance BareB Segment
+
+deriving stock instance (AllBF Show f (Segment Bare)) => Show (Segment Bare f)
+deriving stock instance (AllBF Eq f (Segment Bare)) => Eq (Segment Bare f)
+deriving stock instance (AllBF Show f (Segment Covered)) => Show (Segment Covered f)
+deriving stock instance (AllBF Eq f (Segment Covered)) => Eq (Segment Covered f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "segment", ToLower]] (Segment Bare f) instance (AllBF A.FromJSON f (Segment Bare)) => A.FromJSON (Segment Bare f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "segment", ToLower]] (Segment Bare f) instance (AllBF A.ToJSON f (Segment Bare)) => A.ToJSON (Segment Bare f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "segment", ToLower]] (Segment Covered f) instance (AllBF A.FromJSON f (Segment Covered)) => A.FromJSON (Segment Covered f)
+deriving via CustomJSON '[OmitNothingFields, FieldLabelModifier '[StripPrefix "segment", ToLower]] (Segment Covered f) instance (AllBF A.ToJSON f (Segment Covered)) => A.ToJSON (Segment Covered f)
+
+instance (Alternative f) => Semigroup (Segment Covered f) where
+  (<>) = bzipWith (<|>)
+
+instance (Alternative f) => Monoid (Segment Covered f) where
+  mempty = bpure empty
+
+type ToLower :: Type
+data ToLower
+instance StringModifier ToLower where
+  getStringModifier ""       = ""
+  getStringModifier (c : xs) = toLower c : xs
+
+type StateComplete :: Type
+type StateComplete = State Bare Identity Identity
+
+type StatePatch :: Type
+type StatePatch = State Covered Maybe Maybe
+
+type NightlightComplete :: Type
+type NightlightComplete = Nightlight Bare Identity
+
+type NightlightPatch :: Type
+type NightlightPatch = Nightlight Covered Maybe
+
+type SegmentComplete :: Type
+type SegmentComplete = Segment Bare Identity
+
+type SegmentPatch :: Type
+type SegmentPatch = Segment Covered Maybe
+
+append :: StateComplete -> StatePatch -> StateComplete
+append (State aOn aBri aTransition aPs aPl aNl aLor aMainseg aSeg) (State bOn bBri bTransition bPs bPl bNl bLor bMainseg bSeg) =
+  State (cb aOn bOn) (cb aBri bBri) (cb aTransition bTransition) (cb aPs bPs) (cb aPl bPl) (cb' aNl bNl) (cb aLor bLor) (cb aMainseg bMainseg) (cb'' aSeg bSeg)
+  where
+    cb :: a -> Maybe a -> a
+    cb x dx = runIdentity $ fromMaybeI (Identity x) dx
+    cb' :: (BareB b, ApplicativeB (b Covered)) => b Bare Identity -> Maybe (b Covered Maybe) -> b Bare Identity
+    cb' x = maybe x (append' x)
+    cb'' :: (BareB b, ApplicativeB (b Covered)) => [b Bare Identity] -> Maybe [b Covered Maybe] -> [b Bare Identity]
+    cb'' x = maybe x (zipWith append' x)
+
+append' :: (BareB b, ApplicativeB (b Covered)) => b Bare Identity -> b Covered Maybe -> b Bare Identity
+append' x dx = bstrip $ bzipWith fromMaybeI (bcover x) dx
+
+diff :: StateComplete -> StateComplete -> StatePatch
+diff (State aOn aBri aTransition aPs aPl aNl aLor aMainseg aSeg) (State bOn bBri bTransition bPs bPl bNl bLor bMainseg bSeg) =
+  State (d aOn bOn) (d aBri bBri) (d aTransition bTransition) (d aPs bPs) (d aPl bPl) (d' aNl bNl) (d aLor bLor) (d aMainseg bMainseg) (d'' aSeg bSeg)
+  where
+    d :: Eq a => a -> a -> Maybe a
+    d a b = if a == b then Nothing else Just b
+    d' :: (AllB Eq (b Covered), Eq (b Bare Identity), Monoid (b Covered Maybe), ConstraintsB (b Covered), ApplicativeB (b Covered), BareB b) => b Bare Identity -> b Bare Identity -> Maybe (b Covered Maybe)
+    d' a b = if a == b then Nothing else Just $ diff' a b
+    d'' :: [Segment Bare Identity] -> [Segment Bare Identity] -> Maybe [Segment Covered Maybe]
+    d'' a b = if a == b then Nothing else Just $ zipWith (\i bb -> maybe (bmap (Just . runIdentity) $ bcover bb) (`diff'` bb) (a !? i)) [0..] b ++ replicate (length a - length b) ((mempty :: SegmentPatch) { segmentStop = Just 0 })
+
+#if __GLASGOW_HASKELL__ <= 906
+-- | A total variant of the list index function `(!!)`.
+--
+-- > [2,3,4] !? 1    == Just 3
+-- > [2,3,4] !? (-1) == Nothing
+-- > []      !? 0    == Nothing
+(!?) :: [a] -> Int -> Maybe a
+xs !? n
+  | n < 0     = Nothing
+             -- Definition adapted from GHC.List
+  | otherwise = foldr (\x r k -> case k of
+                                   0 -> Just x
+                                   _ -> r (k-1)) (const Nothing) xs n
+{-# INLINABLE (!?) #-}
+#endif
+
+-- >>> diff' (Nightlight {nightlightOn = False, nightlightDur = 0, nightlightMode = 0, nightlightTbri = 0, nightlightRem = 0}) (Nightlight {nightlightOn = True, nightlightDur = 0, nightlightMode = 0, nightlightTbri = 0, nightlightRem = 0})
+-- Nightlight {nightlightOn = Just True, nightlightDur = Nothing, nightlightMode = Nothing, nightlightTbri = Nothing, nightlightRem = Nothing}
+diff' :: (AllB Eq (b Covered), Eq (b Bare Identity),  Monoid (b Covered Maybe), ConstraintsB (b Covered),  ApplicativeB (b Covered), BareB b) => b Bare Identity -> b Bare Identity -> b Covered Maybe
+diff' a b = if a == b then mempty else bzipWithC @Eq (\aa bb -> if aa == bb then Nothing else Just (runIdentity bb)) (bcover a) (bcover b)
+
+fromMaybeI :: Identity a -> Maybe a -> Identity a
+fromMaybeI (Identity a) Nothing  = Identity a
+fromMaybeI _            (Just a) = Identity a
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,1 @@
+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
diff --git a/test/TypesSpec.hs b/test/TypesSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/TypesSpec.hs
@@ -0,0 +1,90 @@
+{-# LANGUAGE FlexibleInstances   #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+{-# OPTIONS_GHC -Wno-orphans #-}
+{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
+
+{-# HLINT ignore "Monoid law, left identity" #-}
+{-# HLINT ignore "Monoid law, right identity" #-}
+
+module TypesSpec (spec) where
+
+import           Data.Aeson
+import           Data.Aeson.Key
+import           Data.Aeson.KeyMap
+import           Test.Hspec
+import           Test.QuickCheck
+import           WLED.Types
+
+instance Arbitrary StatePatch where
+  arbitrary = State <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary StateComplete where
+  arbitrary = State <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary SegmentPatch where
+  arbitrary = Segment <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+                      <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+                      <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+                      <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary SegmentComplete where
+  arbitrary = Segment <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+                      <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+                      <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+                      <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary NightlightPatch where
+  arbitrary = Nightlight <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+instance Arbitrary NightlightComplete where
+  arbitrary = Nightlight <$> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
+
+prop_associativity :: StatePatch -> StatePatch -> StatePatch -> Bool
+prop_associativity a b c = (a <> b) <> c == a <> (b <> c)
+
+prop_identity :: StatePatch -> Bool
+prop_identity a = mempty <> a == a && a <> mempty == a
+
+prop :: StateComplete -> StatePatch -> Bool
+prop c p = c == append c (diff (append c p) c)
+
+prop_appendIdentity :: StateComplete -> Bool
+prop_appendIdentity c = append c mempty == c
+
+prop_appendAssociativity :: StateComplete -> StatePatch -> StatePatch -> Bool
+prop_appendAssociativity c p1 p2 = append c (p2 <> p1) == append (append c p1) p2
+
+spec :: Spec
+spec = do
+  describe "Types" $ do
+    describe "StatePatch" $ do
+      it "Omit nothing fields" $ do
+        let patch :: StatePatch = (mempty :: StatePatch) { stateBri = Just 1 }
+        let json :: Either String Value = eitherDecode $ encode patch
+        json `shouldBe` Right (Object (fromList [(fromString "bri",Number 1)]))
+      it "should satisfy the associativity law" $
+        property prop_associativity
+      it "should have an identity" $
+        property prop_identity
+      it "what" $ do
+        let p1 :: StatePatch = State {stateOn = Just True, stateBri = Just (-4), stateTransition = Just (-3), statePs = Just (-3), statePl = Just (-3), stateNl = Just (Nightlight {nightlightOn = Just True, nightlightDur = Just (-5), nightlightMode = Just 8, nightlightTbri = Just 4, nightlightRem = Just (-1)}), stateLor = Just 8, stateMainseg = Just 6, stateSeg = Just [Segment {segmentId = Nothing, segmentStart = Just (-1), segmentStop = Nothing, segmentLen = Just (-4), segmentGrp = Nothing, segmentSpc = Nothing, segmentOf = Just (-4), segmentOn = Nothing, segmentFrz = Just True, segmentBri = Just 7, segmentCct = Just (-1), segmentSet = Nothing, segmentCol = Nothing, segmentFx = Just 4, segmentSx = Just 4, segmentIx = Just 7, segmentPal = Just (-7), segmentC1 = Just 2, segmentC2 = Just 5, segmentC3 = Just (-8), segmentSel = Just False, segmentRev = Just False, segmentMi = Just False, segmentO1 = Just True, segmentO2 = Just True, segmentO3 = Nothing, segmentSi = Just 6, segmentM12 = Nothing},Segment {segmentId = Just (-7), segmentStart = Just 5, segmentStop = Just 4, segmentLen = Just (-3), segmentGrp = Just (-4), segmentSpc = Just (-6), segmentOf = Just 4, segmentOn = Just False, segmentFrz = Just False, segmentBri = Just (-5), segmentCct = Just (-3), segmentSet = Just (-6), segmentCol = Nothing, segmentFx = Just (-2), segmentSx = Just (-3), segmentIx = Just 0, segmentPal = Nothing, segmentC1 = Just 4, segmentC2 = Just 0, segmentC3 = Just (-3), segmentSel = Nothing, segmentRev = Just False, segmentMi = Nothing, segmentO1 = Nothing, segmentO2 = Just True, segmentO3 = Just False, segmentSi = Just 8, segmentM12 = Nothing},Segment {segmentId = Just 5, segmentStart = Just 3, segmentStop = Nothing, segmentLen = Just 0, segmentGrp = Just 5, segmentSpc = Nothing, segmentOf = Just 2, segmentOn = Nothing, segmentFrz = Just True, segmentBri = Nothing, segmentCct = Just 6, segmentSet = Just 2, segmentCol = Nothing, segmentFx = Just 8, segmentSx = Just 4, segmentIx = Just 7, segmentPal = Just 6, segmentC1 = Just 6, segmentC2 = Just 7, segmentC3 = Just 1, segmentSel = Just False, segmentRev = Just True, segmentMi = Just False, segmentO1 = Just False, segmentO2 = Just False, segmentO3 = Just True, segmentSi = Just (-8), segmentM12 = Just (-8)},Segment {segmentId = Just 0, segmentStart = Nothing, segmentStop = Nothing, segmentLen = Just (-3), segmentGrp = Nothing, segmentSpc = Just (-1), segmentOf = Just 8, segmentOn = Just False, segmentFrz = Just False, segmentBri = Nothing, segmentCct = Just (-8), segmentSet = Just (-6), segmentCol = Nothing, segmentFx = Just 7, segmentSx = Just (-5), segmentIx = Just 7, segmentPal = Just (-7), segmentC1 = Nothing, segmentC2 = Just (-1), segmentC3 = Nothing, segmentSel = Nothing, segmentRev = Nothing, segmentMi = Just False, segmentO1 = Just True, segmentO2 = Just True, segmentO3 = Just True, segmentSi = Just 4, segmentM12 = Nothing},Segment {segmentId = Nothing, segmentStart = Just 3, segmentStop = Just (-1), segmentLen = Just 2, segmentGrp = Nothing, segmentSpc = Just (-8), segmentOf = Just 5, segmentOn = Just True, segmentFrz = Just False, segmentBri = Just 3, segmentCct = Just (-2), segmentSet = Just (-6), segmentCol = Nothing, segmentFx = Just (-3), segmentSx = Just (-6), segmentIx = Nothing, segmentPal = Just (-3), segmentC1 = Just (-3), segmentC2 = Just 8, segmentC3 = Just (-1), segmentSel = Just False, segmentRev = Just True, segmentMi = Nothing, segmentO1 = Just False, segmentO2 = Nothing, segmentO3 = Just False, segmentSi = Nothing, segmentM12 = Just 7},Segment {segmentId = Just (-2), segmentStart = Just 3, segmentStop = Just 5, segmentLen = Just 7, segmentGrp = Just (-2), segmentSpc = Just 4, segmentOf = Just 3, segmentOn = Just True, segmentFrz = Nothing, segmentBri = Just (-6), segmentCct = Just 3, segmentSet = Nothing, segmentCol = Just [[3,7,6,-5,1,4]], segmentFx = Just (-3), segmentSx = Nothing, segmentIx = Just (-2), segmentPal = Nothing, segmentC1 = Just 8, segmentC2 = Nothing, segmentC3 = Just (-1), segmentSel = Just True, segmentRev = Just False, segmentMi = Just False, segmentO1 = Just True, segmentO2 = Just False, segmentO3 = Nothing, segmentSi = Just (-3), segmentM12 = Just 6}]}
+        let p2 :: StatePatch = State {stateOn = Just True, stateBri = Just 0, stateTransition = Just 5, statePs = Just (-5), statePl = Just 0, stateNl = Nothing, stateLor = Nothing, stateMainseg = Nothing, stateSeg = Just [Segment {segmentId = Just 8, segmentStart = Just (-6), segmentStop = Just 8, segmentLen = Just 4, segmentGrp = Just 8, segmentSpc = Just (-2), segmentOf = Nothing, segmentOn = Nothing, segmentFrz = Just False, segmentBri = Just (-4), segmentCct = Just 4, segmentSet = Nothing, segmentCol = Just [[-2,0,-1,0,-3,1,-8],[1,5,-2,6,7,-8,6],[-1,-4,-2,-4,0,6,4],[-1,1,-3,-2,-6,-8],[1,7,3,7,6,-2,8],[-2,3,-2],[0,5,-4]], segmentFx = Nothing, segmentSx = Just 4, segmentIx = Just (-4), segmentPal = Just 7, segmentC1 = Just (-2), segmentC2 = Nothing, segmentC3 = Nothing, segmentSel = Just False, segmentRev = Just True, segmentMi = Nothing, segmentO1 = Just False, segmentO2 = Just True, segmentO3 = Just True, segmentSi = Just 2, segmentM12 = Nothing},Segment {segmentId = Just (-1), segmentStart = Just (-2), segmentStop = Just 2, segmentLen = Just 0, segmentGrp = Just 1, segmentSpc = Just 5, segmentOf = Just 1, segmentOn = Just False, segmentFrz = Just True, segmentBri = Just 1, segmentCct = Just 6, segmentSet = Just (-5), segmentCol = Just [[4,3,-6,-8,-6,4,8,-2]], segmentFx = Nothing, segmentSx = Just 2, segmentIx = Just 5, segmentPal = Just (-5), segmentC1 = Just 8, segmentC2 = Just 8, segmentC3 = Just 2, segmentSel = Just True, segmentRev = Nothing, segmentMi = Just False, segmentO1 = Just False, segmentO2 = Just False, segmentO3 = Nothing, segmentSi = Just (-1), segmentM12 = Just (-6)},Segment {segmentId = Just 5, segmentStart = Nothing, segmentStop = Just 4, segmentLen = Just (-4), segmentGrp = Just (-6), segmentSpc = Nothing, segmentOf = Just (-2), segmentOn = Just False, segmentFrz = Just True, segmentBri = Just 8, segmentCct = Just (-4), segmentSet = Just (-4), segmentCol = Just [], segmentFx = Just 0, segmentSx = Just (-5), segmentIx = Nothing, segmentPal = Just (-4), segmentC1 = Just 1, segmentC2 = Just 8, segmentC3 = Just (-1), segmentSel = Just False, segmentRev = Just True, segmentMi = Nothing, segmentO1 = Nothing, segmentO2 = Nothing, segmentO3 = Just True, segmentSi = Nothing, segmentM12 = Just (-3)}]}
+        let p3 :: StatePatch = State {stateOn = Just True, stateBri = Just (-3), stateTransition = Nothing, statePs = Just (-8), statePl = Just (-1), stateNl = Just (Nightlight {nightlightOn = Just True, nightlightDur = Just 2, nightlightMode = Just 0, nightlightTbri = Just 1, nightlightRem = Just 1}), stateLor = Just (-8), stateMainseg = Just 0, stateSeg = Just [Segment {segmentId = Just 5, segmentStart = Just (-8), segmentStop = Just 3, segmentLen = Nothing, segmentGrp = Just 3, segmentSpc = Just (-6), segmentOf = Just (-1), segmentOn = Nothing, segmentFrz = Nothing, segmentBri = Just 5, segmentCct = Nothing, segmentSet = Just 3, segmentCol = Nothing, segmentFx = Just 3, segmentSx = Just (-4), segmentIx = Nothing, segmentPal = Nothing, segmentC1 = Just 3, segmentC2 = Just (-3), segmentC3 = Just (-5), segmentSel = Nothing, segmentRev = Just True, segmentMi = Nothing, segmentO1 = Just True, segmentO2 = Just False, segmentO3 = Nothing, segmentSi = Just (-5), segmentM12 = Nothing},Segment {segmentId = Just (-3), segmentStart = Just 3, segmentStop = Just 7, segmentLen = Just 7, segmentGrp = Just (-8), segmentSpc = Just (-2), segmentOf = Just 3, segmentOn = Just False, segmentFrz = Nothing, segmentBri = Just 2, segmentCct = Nothing, segmentSet = Nothing, segmentCol = Just [[4,4],[7,1,1]], segmentFx = Just 8, segmentSx = Just (-7), segmentIx = Just (-8), segmentPal = Just 7, segmentC1 = Just (-2), segmentC2 = Just (-8), segmentC3 = Nothing, segmentSel = Nothing, segmentRev = Nothing, segmentMi = Just True, segmentO1 = Just True, segmentO2 = Nothing, segmentO3 = Just True, segmentSi = Just (-2), segmentM12 = Just (-1)},Segment {segmentId = Just (-8), segmentStart = Nothing, segmentStop = Nothing, segmentLen = Nothing, segmentGrp = Just 3, segmentSpc = Just (-7), segmentOf = Nothing, segmentOn = Just False, segmentFrz = Just False, segmentBri = Just 5, segmentCct = Nothing, segmentSet = Just 3, segmentCol = Just [], segmentFx = Just 5, segmentSx = Just 3, segmentIx = Just 3, segmentPal = Nothing, segmentC1 = Nothing, segmentC2 = Just 4, segmentC3 = Just 1, segmentSel = Nothing, segmentRev = Just True, segmentMi = Just True, segmentO1 = Just True, segmentO2 = Just False, segmentO3 = Just False, segmentSi = Just (-5), segmentM12 = Just 7},Segment {segmentId = Just (-6), segmentStart = Just 1, segmentStop = Nothing, segmentLen = Just (-3), segmentGrp = Just 2, segmentSpc = Just 0, segmentOf = Just (-7), segmentOn = Just True, segmentFrz = Just False, segmentBri = Just 7, segmentCct = Just 7, segmentSet = Just 6, segmentCol = Just [[],[1,6,8],[-6,-8,-7,-8,3,1],[-1,2,-2,8,-6]], segmentFx = Just 3, segmentSx = Just 2, segmentIx = Just 6, segmentPal = Nothing, segmentC1 = Just (-1), segmentC2 = Nothing, segmentC3 = Just 0, segmentSel = Nothing, segmentRev = Just False, segmentMi = Nothing, segmentO1 = Just True, segmentO2 = Just True, segmentO3 = Just True, segmentSi = Just 4, segmentM12 = Just (-3)},Segment {segmentId = Nothing, segmentStart = Just 4, segmentStop = Nothing, segmentLen = Just 6, segmentGrp = Just 6, segmentSpc = Just (-7), segmentOf = Just (-6), segmentOn = Just True, segmentFrz = Nothing, segmentBri = Nothing, segmentCct = Just (-5), segmentSet = Just 1, segmentCol = Nothing, segmentFx = Just 1, segmentSx = Just 0, segmentIx = Just (-1), segmentPal = Nothing, segmentC1 = Just (-5), segmentC2 = Just (-7), segmentC3 = Just (-8), segmentSel = Nothing, segmentRev = Just False, segmentMi = Just False, segmentO1 = Just False, segmentO2 = Just True, segmentO3 = Just False, segmentSi = Just 2, segmentM12 = Nothing},Segment {segmentId = Nothing, segmentStart = Just 0, segmentStop = Just 5, segmentLen = Just 5, segmentGrp = Just (-7), segmentSpc = Just (-3), segmentOf = Just 2, segmentOn = Just False, segmentFrz = Just False, segmentBri = Just 8, segmentCct = Just (-7), segmentSet = Just (-8), segmentCol = Just [], segmentFx = Just 8, segmentSx = Nothing, segmentIx = Just 3, segmentPal = Just 0, segmentC1 = Just 0, segmentC2 = Just (-5), segmentC3 = Just 0, segmentSel = Just True, segmentRev = Just False, segmentMi = Just True, segmentO1 = Just True, segmentO2 = Just True, segmentO3 = Just True, segmentSi = Just 4, segmentM12 = Just (-6)},Segment {segmentId = Nothing, segmentStart = Just (-8), segmentStop = Nothing, segmentLen = Just 3, segmentGrp = Just 1, segmentSpc = Just 3, segmentOf = Just 5, segmentOn = Just True, segmentFrz = Just True, segmentBri = Nothing, segmentCct = Just (-4), segmentSet = Just 1, segmentCol = Just [[6,-1,3,-4,3],[-5,-6,8,4,4,-2],[-2,-1,6,-1,0],[4,-6,6,2,-2],[4],[1,-6,-2,2,4,2]], segmentFx = Nothing, segmentSx = Nothing, segmentIx = Just (-2), segmentPal = Just (-5), segmentC1 = Just 6, segmentC2 = Just 5, segmentC3 = Just (-1), segmentSel = Just True, segmentRev = Just True, segmentMi = Just False, segmentO1 = Just True, segmentO2 = Just True, segmentO3 = Just True, segmentSi = Just (-6), segmentM12 = Just 1},Segment {segmentId = Just 6, segmentStart = Just 2, segmentStop = Just 5, segmentLen = Nothing, segmentGrp = Just (-1), segmentSpc = Nothing, segmentOf = Just (-4), segmentOn = Just False, segmentFrz = Just True, segmentBri = Just 2, segmentCct = Nothing, segmentSet = Just (-7), segmentCol = Just [[-6,-8,-3,-4,-6,0,-6],[-7,2,-3,8],[1,1,-4,-8,4],[0,3,-5],[8,-3,2,-7,1,-5,-2,2],[-8,-2,-4],[3,3,-3,-3,-3,-1,8,4]], segmentFx = Just 3, segmentSx = Nothing, segmentIx = Just (-7), segmentPal = Just (-5), segmentC1 = Just 2, segmentC2 = Just (-5), segmentC3 = Just 0, segmentSel = Nothing, segmentRev = Just True, segmentMi = Just True, segmentO1 = Just False, segmentO2 = Just True, segmentO3 = Just True, segmentSi = Just (-6), segmentM12 = Nothing}]}
+        (p1 <> p2) <> p3 `shouldBe` p1 <> (p2 <> p3)
+    describe "StateComplete" $ do
+      it "appending patches should satisfy the associativity law" $
+        property prop_appendAssociativity
+      it "appending patches should have an identity" $
+        property prop_appendIdentity
+      it "do not append lists" $ do
+        let complete :: StateComplete = State True 255 0 0 0 (Nightlight False 0 0 0 0) 0 0 []
+        let patch :: StatePatch = (mempty :: StatePatch) { stateSeg = Just [(mempty :: SegmentPatch) { segmentBri = Just 255 }] }
+        append complete patch `shouldBe` complete
+      it "append/diff" $
+        property prop
+      it "delete_segments" $ do
+        let c1 :: StateComplete = State {stateOn = False, stateBri = 1, stateTransition = 1, statePs = 1, statePl = 0, stateNl = Nightlight {nightlightOn = True, nightlightDur = 0, nightlightMode = -1, nightlightTbri = -1, nightlightRem = -1}, stateLor = 0, stateMainseg = -1, stateSeg = [Segment {segmentId = 0, segmentStart = 1, segmentStop = -1, segmentLen = -1, segmentGrp = 0, segmentSpc = 1, segmentOf = 0, segmentOn = True, segmentFrz = True, segmentBri = -1, segmentCct = 1, segmentSet = 0, segmentCol = [], segmentFx = 0, segmentSx = 1, segmentIx = -1, segmentPal = 0, segmentC1 = 0, segmentC2 = -1, segmentC3 = 0, segmentSel = False, segmentRev = False, segmentMi = False, segmentO1 = False, segmentO2 = True, segmentO3 = False, segmentSi = -1, segmentM12 = -1}]}
+        let c2 :: StateComplete = State {stateOn = False, stateBri = 1, stateTransition = 1, statePs = 1, statePl = 0, stateNl = Nightlight {nightlightOn = True, nightlightDur = 0, nightlightMode = -1, nightlightTbri = -1, nightlightRem = -1}, stateLor = 0, stateMainseg = -1, stateSeg = []}
+        let clearSegment = fmap (fmap segmentStop) <$> stateSeg $ diff c1 c2
+        clearSegment `shouldBe` Just [Just 0]
diff --git a/wled-json.cabal b/wled-json.cabal
new file mode 100644
--- /dev/null
+++ b/wled-json.cabal
@@ -0,0 +1,84 @@
+cabal-version: 2.2
+
+-- This file has been generated from package.yaml by hpack version 0.37.0.
+--
+-- see: https://github.com/sol/hpack
+
+name:           wled-json
+version:        0.0.1.0
+synopsis:       Convenient interface for interacting with WLED devices
+description:    Please see the README on GitHub at <https://github.com/alaendle/wled-json#readme>
+category:       Interfaces
+homepage:       https://github.com/alaendle/wled-json#readme
+bug-reports:    https://github.com/alaendle/wled-json/issues
+author:         Andreas Ländle
+maintainer:     Andreas Ländle
+copyright:      2024 Andreas Ländle
+license:        BSD-3-Clause
+license-file:   LICENSE
+build-type:     Simple
+extra-source-files:
+    README.md
+extra-doc-files:
+    CHANGELOG.md
+
+source-repository head
+  type: git
+  location: https://github.com/alaendle/wled-json
+
+library
+  exposed-modules:
+      WLED.Device
+      WLED.Octocat.Flags
+      WLED.Types
+  other-modules:
+      Paths_wled_json
+  autogen-modules:
+      Paths_wled_json
+  hs-source-dirs:
+      src
+  ghc-options: -O0 -Weverything -Wno-unsafe -Wno-missing-import-lists -Wno-prepositive-qualified-module -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-safe
+  build-depends:
+      aeson >=2.2.3 && <2.3
+    , barbies >=2.0.5 && <2.2
+    , base >=4.7 && <5
+    , bytestring >=0.11.5.3 && <0.13
+    , deriving-aeson >=0.2.9 && <0.3
+    , http-conduit >=2.3.9.1 && <2.4
+  default-language: Haskell2010
+
+executable wled-json-exe
+  main-is: Main.hs
+  other-modules:
+      Paths_wled_json
+  autogen-modules:
+      Paths_wled_json
+  hs-source-dirs:
+      app
+  ghc-options: -O0 -Weverything -Wno-unsafe -Wno-missing-import-lists -Wno-prepositive-qualified-module -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-safe -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      base >=4.7 && <5
+    , rhine >=1.4.0.1 && <1.6
+    , wled-json
+  default-language: Haskell2010
+
+test-suite wled-json-test
+  type: exitcode-stdio-1.0
+  main-is: Spec.hs
+  other-modules:
+      TypesSpec
+      Paths_wled_json
+  autogen-modules:
+      Paths_wled_json
+  hs-source-dirs:
+      test
+  ghc-options: -O0 -Weverything -Wno-unsafe -Wno-missing-import-lists -Wno-prepositive-qualified-module -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-safe -threaded -rtsopts -with-rtsopts=-N
+  build-depends:
+      QuickCheck
+    , aeson
+    , base >=4.7 && <5
+    , hspec
+    , wled-json
+  default-language: Haskell2010
+  build-tool-depends:
+      hspec-discover:hspec-discover
