Compare commits
2 Commits
fa1e4eb966
...
729d240d0c
Author | SHA1 | Date |
---|---|---|
Florian Schrofner | 729d240d0c | |
Florian Schrofner | bb31d3b274 |
|
@ -0,0 +1,78 @@
|
|||
# Happy Cat 🐈🌞
|
||||
A commandline utility to control your [Elgato Key Light](https://www.elgato.com/de/key-light) and [Key Light Air](https://www.elgato.com/de/key-light-air).
|
||||
Change your light settings easily inside scripts or use configuration files to automatically apply your preferred setup over the course of the day, possibly even exploring the use as a [light therapy](https://en.wikipedia.org/wiki/Light_therapy) device.
|
||||
|
||||
## Usage
|
||||
Happy cat is split up into multiple subcommands, each of which uses its own parameters.
|
||||
To find out more about each command, check out the help pages by appending `--help` after the command.
|
||||
|
||||
*Important: Although the Elgato API uses its own values for light temperature internally, the happy cat command line is intended to use Kelvin. However, the conversion is currently broken and might not work properly.*
|
||||
|
||||
#### Apply
|
||||
Reads the provided configuration file, determines the currently active state and applies the state to the given light once.
|
||||
It's basically a oneshot version of the `daemon` command. For examples of the configuration file, check the corresponding paragraph below.
|
||||
|
||||
*Example*
|
||||
```shell
|
||||
hc apply ./elgato.config elgato.local
|
||||
```
|
||||
|
||||
#### Daemon
|
||||
Reads the given configuration file, then determines and applies the currently valid configuration every minute.
|
||||
|
||||
*Example*
|
||||
```shell
|
||||
hc daemon ./elgato.config elgato.local
|
||||
```
|
||||
|
||||
#### Get
|
||||
Reads and prints the current configuration of the specified light.
|
||||
|
||||
*Example*
|
||||
```shell
|
||||
hc get elgato.local
|
||||
```
|
||||
|
||||
#### Set
|
||||
Sets the given values of powerstatus, brightness and temperature to the specified light.
|
||||
```shell
|
||||
hc set -p ON -b 70 -t 4200 elgato.local
|
||||
```
|
||||
|
||||
## Configuration
|
||||
To automatically apply settings, you can create your own configuration file which defines the settings for each timeframe.
|
||||
All values inside `status` are optional, if they are not defined, those parameters will remain unchanged.
|
||||
Timeframes can cross midnight, but *must not* overlap. If there are overlapping timeframes, the first valid timeframe will be chosen.
|
||||
```json
|
||||
{
|
||||
"config": [
|
||||
{
|
||||
"start": "8:30",
|
||||
"end": "11:00",
|
||||
"status": {
|
||||
"power": "ON",
|
||||
"brightness": 100,
|
||||
"temperature": 6500
|
||||
}
|
||||
},
|
||||
{
|
||||
"start": "11:00",
|
||||
"end": "20:00",
|
||||
"status": {
|
||||
"brightness": 70,
|
||||
"temperature": 4300
|
||||
}
|
||||
},
|
||||
{
|
||||
"start": "20:00",
|
||||
"end": "8:30",
|
||||
"status": {
|
||||
"temperature": 3000
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Automation
|
||||
Probably one could automate the setup using systemd and/or cronjobs, but so far I didn't get to that.
|
|
@ -23,8 +23,6 @@ class ConfigurationRepositoryImpl(
|
|||
val statusToApply = determineCurrentStatus(configuration)
|
||||
|
||||
statusToApply?.let { newStatus ->
|
||||
echo("applying new status:")
|
||||
echo(newStatus.toString())
|
||||
applyLightStatus(lightAddress, port, newStatus)
|
||||
}
|
||||
}
|
||||
|
@ -47,6 +45,8 @@ class ConfigurationRepositoryImpl(
|
|||
private suspend fun applyLightStatus(lightAddress: String, port: Int?, status: LightStatus){
|
||||
val currentStatus = lightRepository.getLightStatus(lightAddress)
|
||||
currentStatus.getNecessaryChanges(status)?.let { statusUpdate ->
|
||||
echo("applying status changes:")
|
||||
echo(statusUpdate)
|
||||
lightRepository.setLightStatus(lightAddress, port, statusUpdate)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,21 @@ import io.ktor.http.*
|
|||
import io.ktor.utils.io.core.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ElgatoLightRepository(private val httpClient: HttpClient): LightRepository {
|
||||
class ElgatoLightRepository: LightRepository, KoinComponent {
|
||||
private val PATH_SEPARATOR = "/"
|
||||
private val DEFAULT_PATH = "elgato"
|
||||
private val LIGHT_PATH = "lights"
|
||||
private val ACCESSORY_INFO_PATH = "accessory-info"
|
||||
|
||||
//needs a new http client for every request
|
||||
private val httpClient: HttpClient get() {
|
||||
return get()
|
||||
}
|
||||
|
||||
val LIGHT_ENDPOINT = listOf(DEFAULT_PATH, LIGHT_PATH)
|
||||
val ACCESSORY_INFO_ENDPOINT = listOf(DEFAULT_PATH, ACCESSORY_INFO_PATH)
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import fi.schro.data.ConfigurationRepositoryImpl
|
|||
import fi.schro.data.ElgatoLightRepository
|
||||
import fi.schro.data.LightRepository
|
||||
import fi.schro.ui.ApplyCommand
|
||||
import fi.schro.ui.DaemonCommand
|
||||
import fi.schro.ui.GetCommand
|
||||
import fi.schro.ui.SetCommand
|
||||
import io.ktor.client.*
|
||||
|
@ -14,17 +15,19 @@ import org.koin.dsl.module
|
|||
|
||||
val commandModule = module {
|
||||
single{ ApplyCommand(get()) }
|
||||
single{ DaemonCommand(get()) }
|
||||
single{ SetCommand(get()) }
|
||||
single{ GetCommand(get()) }
|
||||
}
|
||||
|
||||
val dataModule = module {
|
||||
single<LightRepository> { ElgatoLightRepository(get()) }
|
||||
single<LightRepository> { ElgatoLightRepository() }
|
||||
single<ConfigurationRepository> { ConfigurationRepositoryImpl(get()) }
|
||||
}
|
||||
|
||||
val networkModule = module {
|
||||
single<HttpClient> { HttpClient(CIO){
|
||||
//HttpClient has to be recreated every time it is used
|
||||
factory<HttpClient> { HttpClient(CIO){
|
||||
install(JsonFeature)
|
||||
}}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import com.github.ajalt.clikt.parameters.types.int
|
|||
import fi.schro.data.ConfigurationRepository
|
||||
import fi.schro.data.LightRepository
|
||||
import fi.schro.data.LightStatus
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
|
@ -19,12 +19,14 @@ const val ARG_CONFIGURATION_FILE = "CONFIGURATION_FILE"
|
|||
|
||||
class HappyCatCommand: CliktCommand(name = "hc", help = "A commandline utility to control your elgato keylight"), KoinComponent {
|
||||
private val applyCommand: ApplyCommand by inject()
|
||||
private val daemonCommand: DaemonCommand by inject()
|
||||
private val getCommand: GetCommand by inject()
|
||||
private val setCommand: SetCommand by inject()
|
||||
|
||||
init {
|
||||
subcommands(
|
||||
applyCommand,
|
||||
daemonCommand,
|
||||
getCommand,
|
||||
setCommand
|
||||
)
|
||||
|
@ -80,7 +82,7 @@ class GetCommand(
|
|||
|
||||
class ApplyCommand(
|
||||
private val configurationRepository: ConfigurationRepository
|
||||
): CliktCommand(name = "apply", help = "Applies the given configuration to the specified light"){
|
||||
): CliktCommand(name = "apply", help = "Applies the currently valid configuration inside the configuration file to the specified light"){
|
||||
private val configurationFile: String by argument(ARG_CONFIGURATION_FILE)
|
||||
private val targetLamp: String by argument(ARG_TARGET_LAMP)
|
||||
|
||||
|
@ -91,6 +93,27 @@ class ApplyCommand(
|
|||
}
|
||||
}
|
||||
|
||||
class DaemonCommand(
|
||||
private val configurationRepository: ConfigurationRepository
|
||||
): CliktCommand(name = "daemon", help = "Starts a daemon which applies the currently valid configuration inside the configuration file every minute"){
|
||||
private val configurationFile: String by argument(ARG_CONFIGURATION_FILE)
|
||||
private val targetLamp: String by argument(ARG_TARGET_LAMP)
|
||||
|
||||
override fun run() {
|
||||
runBlocking {
|
||||
while(isActive){
|
||||
//continue daemon even if applying configuration failed once
|
||||
try {
|
||||
configurationRepository.applyConfiguration(configurationFile, targetLamp)
|
||||
} catch (exception: Exception){
|
||||
echo(exception)
|
||||
}
|
||||
delay(1000 * 60)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class LightPowerStatus(val stringValue: String, val intValue: Int) {
|
||||
ON("ON", 1),
|
||||
OFF("OFF", 0);
|
||||
|
|
Loading…
Reference in New Issue