packages feed

wled-json (empty) → 0.0.1.0

raw patch · 11 files changed

+658/−0 lines, 11 filesdep +QuickCheckdep +aesondep +barbiessetup-changed

Dependencies added: QuickCheck, aeson, barbies, base, bytestring, deriving-aeson, hspec, http-conduit, rhine, wled-json

Files

+ CHANGELOG.md view
@@ -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)
+ LICENSE view
@@ -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.
+ README.md view
@@ -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)
+ Setup.hs view
@@ -0,0 +1,2 @@+import           Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -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"
+ src/WLED/Device.hs view
@@ -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)+
+ src/WLED/Octocat/Flags.hs view
@@ -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 }
+ src/WLED/Types.hs view
@@ -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
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/TypesSpec.hs view
@@ -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]
+ wled-json.cabal view
@@ -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