home-assistant-client (empty) → 0.1.0.0
raw patch · 12 files changed
+762/−0 lines, 12 filesdep +aesondep +basedep +bytestringsetup-changed
Dependencies added: aeson, base, bytestring, containers, deriving-aeson, exceptions, home-assistant-client, http-client, http-client-tls, http-types, optparse-applicative, servant, servant-client, text
Files
- CHANGELOG.md +11/−0
- LICENSE +26/−0
- README.md +36/−0
- Setup.hs +2/−0
- app/Main.hs +88/−0
- app/Options.hs +110/−0
- home-assistant-client.cabal +144/−0
- src/HomeAssistant/Client.hs +98/−0
- src/HomeAssistant/Common/HomeAssistant.hs +31/−0
- src/HomeAssistant/Common/Notify.hs +138/−0
- src/HomeAssistant/Types.hs +76/−0
- test/Spec.hs +2/−0
+ CHANGELOG.md view
@@ -0,0 +1,11 @@+# Changelog for `home-assistant-client`++All notable changes to this project will be documented in this file.++The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),+and this project adheres to the+[Haskell Package Versioning Policy](https://pvp.haskell.org/).++## 0.1.0.0 - 2025-08-10++- Initial version with partial support for the Home Assistant REST API
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright 2025 Michael B. Gale++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,36 @@+# home-assistant-client: Haskell client for the Home Assistant API+++[](https://github.com/mbg/home-assistant-client/actions/workflows/haskell.yml)+[](https://github.com/mbg/home-assistant-client/actions/workflows/nightly.yml)++A Haskell library for the [Home Assistant API](https://developers.home-assistant.io/docs/api/rest/), allowing you to write Haskell applications which interact with [Home Assistant](https://www.home-assistant.io).++A `ha-client` program that exposes the library functions as CLI commands is also provided.++## Library usage++The Home Assistant API offers fairly general endpoints, for which the `HomeAssistant.Client` module provides client computations. All API endpoints require authorisation with a [long-lived token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token). For example, to retrieve a list available services from Home Assistant:++```haskell+import Network.HTTP.Client++defaultAddress :: String+defaultAddress = "http://homeassistant.local:8123/"++main :: IO ()+main = do+ -- `token` should be the long-lived token+ httpManager <- defaultManagerSettings+ address <- parseBaseUrl defaultAddress++ let env = mkHomeAssistantEnv token httpManager address+ result <- runClientM services env+ print result+```++## CLI usage++You must set an environment variable named `HA_TOKEN` to your [long-lived token](https://developers.home-assistant.io/docs/auth_api/#long-lived-access-token). The CLI also inspects the `HA_ADDRESS` environment variable to determine the address of the Home Assistant server. This is optional and will default to `http://homeassistant.local:8123/` if no value is set.++The `ha-client` CLI can be invoked with `--help` to retrieve usage information. All sub-commands also support the `--help` flag to retrieve command-specific help.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,88 @@+module Main (main) where++--------------------------------------------------------------------------------++import Control.Monad.IO.Class++import qualified Data.Aeson as JSON+import qualified Data.ByteString.Char8 as C8+import qualified Data.ByteString.Lazy as LBS+import Data.Maybe++import System.Environment+import System.Exit++import Network.HTTP.Client+import Network.HTTP.Client.TLS++import HomeAssistant.Client+import HomeAssistant.Common.HomeAssistant+import HomeAssistant.Common.Notify++import Options++--------------------------------------------------------------------------------++-- | This is the default address that Home Assistant installations can be found at.+-- Used only if no address is explicitly specified.+defaultAddress :: String+defaultAddress = "http://homeassistant.local:8123/"++--------------------------------------------------------------------------------++managerForBaseUrl :: BaseUrl -> IO Manager+managerForBaseUrl address = newManager $ case baseUrlScheme address of+ Http -> defaultManagerSettings+ Https -> tlsManagerSettings++--------------------------------------------------------------------------------++runAndFormat :: JSON.ToJSON v => ClientEnv -> HA v -> IO ()+runAndFormat env m = runClientM m env >>= \case+ Left err -> print err+ Right v -> LBS.putStr (JSON.encode v)++runConfigCmd :: ClientEnv -> ConfigCommand -> IO ()+runConfigCmd env ShowConfig = runAndFormat env config+runConfigCmd env CheckConfig = runAndFormat env $ do+ result <- check+ case result of+ JSON.Error err -> liftIO $ do+ print err+ exitFailure+ JSON.Success v -> pure v+runConfigCmd env ReloadAllConfig = runAndFormat env reloadAll++runServiceCmd :: ClientEnv -> ServiceCommand -> IO ()+runServiceCmd env ListServices = runAndFormat env services++runNotificationCmd :: ClientEnv -> NotificationCommand -> IO ()+runNotificationCmd env SendNotification{..} = runAndFormat env $ do+ let notificationData = OtherNotificationData <$> sendNotificationData+ payload = setNotificationData notificationData $ notification sendNotificationMessage+ notify sendNotificationDevice payload++-- | 'main' is the main entry point for this application.+main :: IO ()+main = do+ -- Parse command-line arguments+ opts <- parseCmdLineArgs++ mToken <- fmap C8.pack <$> lookupEnv "HA_TOKEN"+ address <- fromMaybe defaultAddress <$> lookupEnv "HA_ADDRESS" >>= parseBaseUrl++ case mToken of+ Nothing -> do+ putStrLn "Error: HA_TOKEN must be set"+ exitFailure+ Just token -> do+ httpManager <- managerForBaseUrl address++ let env = mkHomeAssistantEnv token httpManager address++ case clientOptionsCommand opts of+ ConfigCommand cmd -> runConfigCmd env cmd+ ServiceCommand cmd -> runServiceCmd env cmd+ NotificationCommand cmd -> runNotificationCmd env cmd++--------------------------------------------------------------------------------
+ app/Options.hs view
@@ -0,0 +1,110 @@+-- | Contains types representing the command-line configuration options of the CLI+-- along with parsers to parse command-line arguments into configuration values.+module Options (+ ConfigCommand(..),+ ServiceCommand(..),+ NotificationCommand(..),+ ClientCommand(..),+ ClientOptions(..),+ parseCmdLineArgs+) where++--------------------------------------------------------------------------------++import Data.Aeson+import Data.Text+import Data.ByteString.Lazy.Char8 qualified as C8++import Options.Applicative++--------------------------------------------------------------------------------++-- | Represents commands related to the Home Assistant configuration.+data ConfigCommand+ = ShowConfig+ | CheckConfig+ | ReloadAllConfig++showConfigP :: Parser ConfigCommand+showConfigP = pure ShowConfig++checkConfigP :: Parser ConfigCommand+checkConfigP = pure ShowConfig++reloadAllConfigP :: Parser ConfigCommand+reloadAllConfigP = pure ReloadAllConfig++--------------------------------------------------------------------------------++-- | Represents commands related to Home Assistant services.+data ServiceCommand+ = ListServices++listServicesP :: Parser ServiceCommand+listServicesP = pure ListServices++--------------------------------------------------------------------------------++-- | Represents commands related to the notification service.+data NotificationCommand+ = SendNotification {+ -- | The name of the service to send the notification to.+ sendNotificationDevice :: Text,+ -- | The message to send.+ sendNotificationMessage :: Text,+ -- | Arbitrary data to send.+ sendNotificationData :: Maybe Value+ }++sendNotificationP :: Parser NotificationCommand+sendNotificationP = SendNotification <$>+ argument str (metavar "DEVICE") <*>+ argument str (metavar "MESSAGE") <*>+ optional (option (eitherReader (eitherDecode . C8.pack)) (long "data"))++--------------------------------------------------------------------------------++data ClientCommand+ = ConfigCommand ConfigCommand+ | ServiceCommand ServiceCommand+ | NotificationCommand NotificationCommand++configP :: Parser ClientCommand+configP = fmap ConfigCommand $ subparser $+ command "show" (info (showConfigP <**> helper) (progDesc "Show the Home Assistant configuration")) <>+ command "check" (info (checkConfigP <**> helper) (progDesc "Check the Home Assistant configuration")) <>+ command "reload-all" (info (reloadAllConfigP <**> helper) (progDesc "Reloads all Home Assistant configurations"))++servicesP :: Parser ClientCommand+servicesP = fmap ServiceCommand $ subparser $+ command "list" (info (listServicesP <**> helper) (progDesc "Lists all Home Assistant services"))++notificationP :: Parser ClientCommand+notificationP = fmap NotificationCommand $ subparser $+ command "send" (info (sendNotificationP <**> helper) (progDesc "Send a notification"))++clientCommandP :: Parser ClientCommand+clientCommandP = subparser $+ command "config" (info (configP <**> helper) (progDesc "Configuration commands")) <>+ command "services" (info (servicesP <**> helper) (progDesc "Services commands")) <>+ command "notification" (info (notificationP <**> helper) (progDesc "Notification service commands"))++--------------------------------------------------------------------------------++data ClientOptions = MkClientOptions {+ clientAddress :: Maybe Text,+ clientOptionsCommand :: ClientCommand+}++optionsP :: Parser ClientOptions+optionsP = MkClientOptions+ <$> optional (strOption (long "address" <> metavar "URL" <> help "The address of the Home Assistant server"))+ <*> clientCommandP++opts :: ParserInfo ClientOptions+opts = info (optionsP <**> helper) idm++parseCmdLineArgs :: IO ClientOptions+parseCmdLineArgs = execParser opts++--------------------------------------------------------------------------------
+ home-assistant-client.cabal view
@@ -0,0 +1,144 @@+cabal-version: 2.2++-- This file has been generated from package.yaml by hpack version 0.38.1.+--+-- see: https://github.com/sol/hpack++name: home-assistant-client+version: 0.1.0.0+synopsis: Client library for the Home Assistant API.+description: Please see the README on GitHub at <https://github.com/mbg/home-assistant-client#readme>+category: Web+homepage: https://github.com/mbg/home-assistant-client#readme+bug-reports: https://github.com/mbg/home-assistant-client/issues+author: Michael B. Gale+maintainer: github@michael-gale.co.uk+copyright: 2025 Michael B. Gale+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/mbg/home-assistant-client++library+ exposed-modules:+ HomeAssistant.Client+ HomeAssistant.Common.HomeAssistant+ HomeAssistant.Common.Notify+ HomeAssistant.Types+ other-modules:+ Paths_home_assistant_client+ autogen-modules:+ Paths_home_assistant_client+ hs-source-dirs:+ src+ default-extensions:+ DataKinds+ DeriveGeneric+ DerivingVia+ FlexibleInstances+ GADTs+ ImportQualifiedPost+ KindSignatures+ LambdaCase+ OverloadedStrings+ RecordWildCards+ TypeOperators+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+ build-depends:+ aeson >=2.2.3 && <2.3+ , base >=4.7 && <5+ , bytestring >=0.12.1 && <0.13+ , containers >=0.6.8 && <0.8+ , deriving-aeson >=0.2.10 && <0.3+ , exceptions >=0.10.9 && <0.11+ , http-client >=0.7.19 && <0.8+ , http-client-tls >=0.3.6 && <0.4+ , http-types >=0.12.4 && <0.13+ , servant >=0.20.3 && <0.21+ , servant-client >=0.20.3 && <0.21+ , text >=2.1.1 && <2.2+ default-language: Haskell2010++executable ha-client+ main-is: Main.hs+ other-modules:+ Options+ Paths_home_assistant_client+ autogen-modules:+ Paths_home_assistant_client+ hs-source-dirs:+ app+ default-extensions:+ DataKinds+ DeriveGeneric+ DerivingVia+ FlexibleInstances+ GADTs+ ImportQualifiedPost+ KindSignatures+ LambdaCase+ OverloadedStrings+ RecordWildCards+ TypeOperators+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ aeson >=2.2.3 && <2.3+ , base >=4.7 && <5+ , bytestring >=0.12.1 && <0.13+ , containers >=0.6.8 && <0.8+ , deriving-aeson >=0.2.10 && <0.3+ , exceptions >=0.10.9 && <0.11+ , home-assistant-client+ , http-client >=0.7.19 && <0.8+ , http-client-tls >=0.3.6 && <0.4+ , http-types >=0.12.4 && <0.13+ , optparse-applicative >=0.18.0 && <0.20+ , servant >=0.20.3 && <0.21+ , servant-client >=0.20.3 && <0.21+ , text >=2.1.1 && <2.2+ default-language: Haskell2010++test-suite home-assistant-client-test+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules:+ Paths_home_assistant_client+ autogen-modules:+ Paths_home_assistant_client+ hs-source-dirs:+ test+ default-extensions:+ DataKinds+ DeriveGeneric+ DerivingVia+ FlexibleInstances+ GADTs+ ImportQualifiedPost+ KindSignatures+ LambdaCase+ OverloadedStrings+ RecordWildCards+ TypeOperators+ ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ aeson >=2.2.3 && <2.3+ , base >=4.7 && <5+ , bytestring >=0.12.1 && <0.13+ , containers >=0.6.8 && <0.8+ , deriving-aeson >=0.2.10 && <0.3+ , exceptions >=0.10.9 && <0.11+ , home-assistant-client+ , http-client >=0.7.19 && <0.8+ , http-client-tls >=0.3.6 && <0.4+ , http-types >=0.12.4 && <0.13+ , servant >=0.20.3 && <0.21+ , servant-client >=0.20.3 && <0.21+ , text >=2.1.1 && <2.2+ default-language: Haskell2010
+ src/HomeAssistant/Client.hs view
@@ -0,0 +1,98 @@+-- | Client library for the Home Assistant API.+module HomeAssistant.Client (+ API,+ HA,+ JSONOptions,+ StateChange,+ status,+ config,+ services,+ callService,+ mkHomeAssistantEnv,+ module Servant.Client+) where++--------------------------------------------------------------------------------++import Data.Aeson+import Data.Data+import Data.Text+import Data.ByteString.Char8 qualified as C8++import Deriving.Aeson++import Network.HTTP.Client (Manager, requestHeaders)+import Network.HTTP.Types++import Servant.API+import Servant.Client++import HomeAssistant.Types++--------------------------------------------------------------------------------++-- | The Home Assistant API as a type.+type API = "api" :> Endpoints+type Endpoints+ = Get '[JSON] Value+ :<|> "config" :>+ Get '[JSON] Config+ :<|> "services" :>+ Get '[JSON] [ServiceDomain]+ :<|> "services" :>+ Capture "domain" Text :>+ Capture "service" Text :>+ ReqBody '[JSON] (Maybe Value) :>+ Post '[JSON] Value++api :: Proxy API+api = Proxy++type HA = ClientM++--------------------------------------------------------------------------------++status :: HA Value+config :: HA Config+services :: HA [ServiceDomain]++--------------------------------------------------------------------------------++-- | Represents information about the state of an entity.+data StateChange = MkStateChange {+ stateChangeAttributes :: Value,+ stateChangeEntityID :: Text,+ stateChangeLastChanged :: Text,+ stateChangeState :: Text+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "stateChange") StateChange++-- | 'callService' @domain service body@ calls @service@ in @domain@ with an+-- optional @body@. The result depends on the service.+callService :: Text -> Text -> Maybe Value -> HA Value++--------------------------------------------------------------------------------++status+ :<|> config+ :<|> services+ :<|> callService = client api++--------------------------------------------------------------------------------++authorize :: C8.ByteString -> ClientEnv -> ClientEnv+authorize token env = env{ makeClientRequest = mkRequest } where+ mkRequest url req = do+ -- Construct the request using the default function.+ baseReq <- defaultMakeClientRequest url req+ -- Then inject the Authorization header with the HA token.+ pure baseReq{+ requestHeaders =+ (hAuthorization, "Bearer " <> token) : requestHeaders baseReq+ }++mkHomeAssistantEnv :: C8.ByteString -> Manager -> BaseUrl -> ClientEnv+mkHomeAssistantEnv token httpManager address =+ authorize token $ mkClientEnv httpManager address++--------------------------------------------------------------------------------
+ src/HomeAssistant/Common/HomeAssistant.hs view
@@ -0,0 +1,31 @@+-- | Implements+module HomeAssistant.Common.HomeAssistant (+ check,+ reloadAll+) where++--------------------------------------------------------------------------------++import Data.Aeson+import qualified Data.Text as T++import HomeAssistant.Client ( HA, callService )+import HomeAssistant.Types ( Config )++--------------------------------------------------------------------------------++-- | The name of the Home Assistant service.+serviceName :: T.Text+serviceName = "homeassistant"++--------------------------------------------------------------------------------++-- | 'notify' @device notification@ is a wrapper around `callService` that uses+-- the notifications service to send @notification@ to @device@.+check :: HA (Result Config)+check = fromJSON <$> callService serviceName "check_config" Nothing++reloadAll :: HA Value+reloadAll = callService serviceName "reload_all" Nothing++--------------------------------------------------------------------------------
+ src/HomeAssistant/Common/Notify.hs view
@@ -0,0 +1,138 @@+-- | Implements+module HomeAssistant.Common.Notify (+ NotificationData(..),+ Notification,+ notification,+ setNotificationTitle,+ setNotificationData,+ notify+) where++--------------------------------------------------------------------------------++import Data.Aeson+import qualified Data.Text as T++import Deriving.Aeson++import HomeAssistant.Client++--------------------------------------------------------------------------------++-- | The name of the notifications service.+serviceName :: T.Text+serviceName = "notify"++-- | Enumerates different notification platforms that are supported by HA.+-- Different platforms have different capabilities and understand different+-- options in their payloads.+data NotificationPlatform+ -- | Generic.+ = OtherPlatform+ -- | iOS/macOS+ | Apple+ deriving (Eq, Show)++-- |+-- See https://companion.home-assistant.io/docs/notifications/actionable-notifications.+data NotificationAction = MkNotificationAction {+ -- | The text to display on the button.+ notificationActionTitle :: T.Text,+ -- | The key data to send to HA when the button is pressed.+ --+ -- Can be set to the string @"REPLY"@ to prompt for the string to return to HA.+ --+ -- On Android, this must be set to the string @"URI"@ to open the optional URI that can be specified.+ notificationActionAction :: T.Text,+ -- | Optional, the URI to open when the button is pressed.+ notificationActionURI :: Maybe T.Text,+ -- | Set to @"textInput"@ to prompt for text.+ notificationActionBehavior :: Maybe T.Text+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "notificationAction") NotificationAction++data AppleNotificationData = MkAppleNotificationData {+ -- | A URI to open when the notification is clicked on,+ -- see https://companion.home-assistant.io/docs/notifications/notifications-basic/#opening-a-url+ appleNotificationUrl :: T.Text,+ -- | A list of actions that should be available as buttons when the notification is pressed.+ appleNotificationAction :: Maybe [NotificationAction]+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "appleNotification") AppleNotificationData+++-- | Represents platform-specific data for a notification.+data NotificationData (platform :: NotificationPlatform) where+ -- | A general constructor that can be used to pass arbitrary JSON data to+ -- the notification provider.+ OtherNotificationData :: Value -> NotificationData 'OtherPlatform+ AppleNotificationData :: AppleNotificationData -> NotificationData 'Apple++instance Eq (NotificationData platform) where+ (OtherNotificationData l) == (OtherNotificationData r) = l == r+ (AppleNotificationData l) == (AppleNotificationData r) = l == r++instance Show (NotificationData platform) where+ show (OtherNotificationData d) = show d+ show (AppleNotificationData d) = show d++instance FromJSON (NotificationData OtherPlatform) where+ parseJSON = fmap OtherNotificationData . parseJSON++instance FromJSON (NotificationData Apple) where+ parseJSON = fmap AppleNotificationData . parseJSON++instance ToJSON (NotificationData platform) where+ toJSON (OtherNotificationData v) = toJSON v+ toJSON (AppleNotificationData v) = toJSON v++data Notification platform = MkNotification {+ -- | The notification message. This is required.+ notificationMessage :: T.Text,+ -- | The title of the notification. Will default to the Home Assistant app name on the+ -- target device if not set.+ notificationTitle :: Maybe T.Text,+ -- | Platform-specific data associated with the notification.+ --+ -- See the constructors of `NotificationData` for more information and the documentation+ -- at https://www.home-assistant.io/integrations/notify/+ notificationData :: Maybe (NotificationData platform),+ -- | Platform-specific target for the notification.+ notificationTarget :: Maybe Value+} deriving (Generic, Eq, Show)+ deriving (ToJSON) via CustomJSON (JSONOptions "notification") (Notification platform)++instance FromJSON (Notification OtherPlatform) where+ parseJSON = withObject "Notification" $ \v ->+ MkNotification <$> v .: "message" <*> v .: "title" <*> v .: "data" <*> v .: "target"++-- | 'notification' @message@ constructs a `Notification` containing @message@.+-- Can be used to then change other properties of the resulting `Notification`.+notification :: T.Text -> Notification platform+notification message = MkNotification{+ notificationMessage = message,+ notificationTitle = Nothing,+ notificationData = Nothing,+ notificationTarget = Nothing+}++-- | 'setNotificationTitle' @title notification@ sets @notification@'s @title@.+setNotificationTitle ::+ Maybe T.Text ->+ Notification platform ->+ Notification platform+setNotificationTitle title n = n{ notificationTitle = title }++-- | 'setNotificationData' @data notification@ sets @notification@'s @data@.+setNotificationData ::+ Maybe (NotificationData platform) ->+ Notification platform ->+ Notification platform+setNotificationData d n = n{ notificationData = d }++-- | 'notify' @device notification@ is a wrapper around `callService` that uses+-- the notifications service to send @notification@ to @device@.+notify :: T.Text -> Notification platform -> HA Value+notify device = callService serviceName device . Just . toJSON++--------------------------------------------------------------------------------
+ src/HomeAssistant/Types.hs view
@@ -0,0 +1,76 @@+-- | Types used by Home Assistant.+module HomeAssistant.Types (+ JSONOptions,+ UnitSystem(..),+ Config(..),+ Service(..),+ ServiceDomain(..)+) where++--------------------------------------------------------------------------------++import Data.Aeson+import Data.Text+import Data.Map.Lazy qualified as M++import Deriving.Aeson++import GHC.TypeLits++--------------------------------------------------------------------------------++-- | The custom JSON encoding/decoding options for HA types.+--+-- HA does not like fields that are set to @null@ - they must be omitted.+type JSONOptions (prefix :: Symbol) =+ '[OmitNothingFields, FieldLabelModifier '[StripPrefix prefix, CamelToSnake]]++--------------------------------------------------------------------------------++-- | Represents units that Home Assistant is configured to use.+data UnitSystem = MkUnitSystem {+ -- | Identifies the unit used for distances.+ unitSystemLength :: Text,+ -- | Identifies the unit used for mass.+ unitSystemMass :: Text,+ -- | Identifies the unit used for temperatures.+ unitSystemTemperature :: Text,+ -- | Identifies the unit used for volumes.+ unitSystemVolume :: Text+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "unitSystem") UnitSystem++-- | Represents Home Assistant configurations.+data Config = MkConfig {+ configComponents :: [Text],+ configConfigDir :: Text,+ configLocationName :: Text,+ configTimeZone :: Text,+ configElevation :: Int,+ configLatitude :: Double,+ configLongitude :: Double,+ -- | The units that Home Assistant is configured to use.+ configUnitSystem :: UnitSystem,+ configVersion :: Text,+ configWhitelistExternalDirs :: [Text]+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "config") Config++-- | Represents Home Assistant services.+data Service = MkService {+ serviceName :: Text,+ serviceDescription :: Text,+ serviceFields :: M.Map Text Value+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "service") Service++-- | Represents Home Assistant service domains.+data ServiceDomain = MkServiceDomain {+ -- | The domain name.+ sdDomain :: Text,+ -- | The services in this domain as a mapping from names to services.+ sdServices :: M.Map Text Service+} deriving (Generic, Eq, Show)+ deriving (FromJSON, ToJSON) via CustomJSON (JSONOptions "sd") ServiceDomain++--------------------------------------------------------------------------------
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"