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 +15/−0
- LICENSE +26/−0
- README.md +69/−0
- Setup.hs +2/−0
- app/Main.hs +45/−0
- src/WLED/Device.hs +57/−0
- src/WLED/Octocat/Flags.hs +26/−0
- src/WLED/Types.hs +243/−0
- test/Spec.hs +1/−0
- test/TypesSpec.hs +90/−0
- wled-json.cabal +84/−0
+ 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