-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add support for gen 2 devices (3EM pro)
- Loading branch information
Showing
26 changed files
with
1,142 additions
and
392 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
102 changes: 102 additions & 0 deletions
102
src/main/kotlin/click/dobel/shelly/exporter/client/HttpClientBase.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package click.dobel.shelly.exporter.client | ||
|
||
import click.dobel.shelly.exporter.config.ShellyConfigProperties | ||
import org.apache.hc.client5.http.auth.UsernamePasswordCredentials | ||
import org.apache.hc.client5.http.config.ConnectionConfig | ||
import org.apache.hc.client5.http.impl.classic.HttpClients | ||
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder | ||
import org.apache.hc.core5.http.io.SocketConfig | ||
import org.apache.hc.core5.pool.PoolConcurrencyPolicy | ||
import org.apache.hc.core5.util.TimeValue | ||
import org.apache.hc.core5.util.Timeout | ||
import org.springframework.boot.web.client.RestTemplateBuilder | ||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory | ||
import org.springframework.web.client.RestTemplate | ||
|
||
fun url( | ||
address: String, | ||
path: String | ||
): String = "http://${address}${slash(path)}" | ||
|
||
private fun slash( | ||
path: String | ||
): String = if (path.startsWith("/")) path else "/$path" | ||
|
||
private fun <T> T.runIf(condition: Boolean, block: T.() -> T): T { | ||
return if (condition) | ||
block() | ||
else | ||
this | ||
} | ||
|
||
/* | ||
fun createRestTemplate( | ||
auth: ShellyConfigProperties.Auth, | ||
httpParams: ShellyConfigProperties.HttpParams, | ||
builder: RestTemplateBuilder, | ||
): RestTemplate { | ||
return builder | ||
.setConnectTimeout(httpParams.connectTimeout) | ||
.setReadTimeout(httpParams.requestTimeout) | ||
.runIf(auth.isEnabled) { | ||
basicAuthentication( | ||
auth.username, | ||
auth.password | ||
) | ||
} | ||
.build() | ||
} | ||
*/ | ||
|
||
fun RestTemplateBuilder.createRestTemplate( | ||
auth: ShellyConfigProperties.Auth, | ||
httpParams: ShellyConfigProperties.HttpParams, | ||
): RestTemplate { | ||
val httpClient = httpClient( | ||
auth, | ||
httpParams, | ||
) | ||
|
||
return this | ||
.requestFactory { -> HttpComponentsClientHttpRequestFactory(httpClient) } | ||
.build() | ||
} | ||
|
||
private fun httpClient( | ||
auth: ShellyConfigProperties.Auth, | ||
httpParams: ShellyConfigProperties.HttpParams, | ||
) = HttpClients | ||
.custom() | ||
.runIf(auth.isEnabled) { | ||
val credentials = UsernamePasswordCredentials( | ||
auth.username, | ||
auth.password.toCharArray() | ||
) | ||
setDefaultCredentialsProvider { _, _ -> credentials } | ||
} | ||
.disableCookieManagement() | ||
.disableRedirectHandling() | ||
.disableAuthCaching() | ||
.disableConnectionState() | ||
.setConnectionReuseStrategy { _, _, _ -> false } | ||
.setConnectionManager( | ||
PoolingHttpClientConnectionManagerBuilder.create() | ||
.setMaxConnTotal(httpParams.maxConnectionsTotal) | ||
.setMaxConnPerRoute(httpParams.maxConnectionsPerRoute) | ||
.setPoolConcurrencyPolicy(PoolConcurrencyPolicy.LAX) | ||
.setDefaultSocketConfig( | ||
SocketConfig.custom() | ||
.setSoTimeout(Timeout.of(httpParams.requestTimeout)) | ||
.build() | ||
) | ||
.setDefaultConnectionConfig( | ||
ConnectionConfig.custom() | ||
.setConnectTimeout(Timeout.of(httpParams.connectTimeout)) | ||
.setSocketTimeout(Timeout.of(httpParams.requestTimeout)) | ||
.setTimeToLive(TimeValue.of(httpParams.timeToLive)) | ||
.setValidateAfterInactivity(Timeout.of(httpParams.validationPeriod)) | ||
.build() | ||
) | ||
.build() | ||
) | ||
.build() |
73 changes: 27 additions & 46 deletions
73
src/main/kotlin/click/dobel/shelly/exporter/client/ShellyClient.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,45 @@ | ||
package click.dobel.shelly.exporter.client | ||
|
||
import click.dobel.shelly.exporter.client.api.Settings | ||
import click.dobel.shelly.exporter.client.api.Shelly | ||
import click.dobel.shelly.exporter.client.api.Status | ||
import click.dobel.shelly.exporter.config.ShellyConfigProperties | ||
import org.springframework.boot.web.client.RestTemplateBuilder | ||
import org.springframework.cache.annotation.Cacheable | ||
import org.springframework.stereotype.Component | ||
import mu.KLoggable | ||
import org.springframework.web.client.RestTemplate | ||
|
||
@Component | ||
class ShellyClient( | ||
configProperties: ShellyConfigProperties, | ||
restTemplateBuilder: RestTemplateBuilder | ||
abstract class ShellyClient( | ||
loggable: KLoggable | ||
) { | ||
|
||
@Cacheable("Status", sync = true) | ||
fun status(address: String) = get<Status>(address, "status") | ||
|
||
@Cacheable("Shelly", sync = true) | ||
fun shelly(address: String) = get<Shelly>(address, "shelly") | ||
companion object { | ||
const val RETRIES = 3 | ||
} | ||
|
||
@Cacheable("Settings", sync = true) | ||
fun settings(address: String) = get<Settings>(address, "settings") | ||
protected abstract val restTemplate: RestTemplate | ||
protected val logger = loggable.logger | ||
|
||
private inline fun <reified T : Any> get( | ||
protected inline fun <reified T : Any> get( | ||
address: String, | ||
path: String | ||
): T? { | ||
return try { | ||
restTemplate.getForObject(url(address, path)) | ||
} catch (ex: Exception) { | ||
val url = url(address, path) | ||
return runCatching { | ||
retry(RETRIES) { | ||
restTemplate.getForObject<T>(url) | ||
} | ||
}.getOrElse { ex -> | ||
logger.warn { "GET ${url}: HTTP Request failure: ${ex.message}" } | ||
null | ||
} | ||
} | ||
|
||
private fun url( | ||
address: String, | ||
path: String | ||
): String = "http://${address}${slash(path)}" | ||
|
||
private fun slash( | ||
path: String | ||
): String = if (path.startsWith("/")) path else "/$path" | ||
|
||
private val restTemplate: RestTemplate = restTemplateBuilder | ||
.setConnectTimeout(configProperties.devices.connectTimeout) | ||
.setReadTimeout(configProperties.devices.requestTimeout) | ||
.runIf(configProperties.hasAuth) { | ||
basicAuthentication( | ||
configProperties.auth.username, | ||
configProperties.auth.password | ||
) | ||
inline fun <T> retry( | ||
retries: Int = 1, | ||
call: () -> T | ||
): T? { | ||
retryLoop@ for (i in 0..retries) { | ||
val result = runCatching(call) | ||
if (result.isFailure && i < retries) { | ||
continue@retryLoop | ||
} | ||
return result.getOrThrow() | ||
} | ||
.build() | ||
|
||
private inline fun <T> T.runIf(condition: Boolean, block: T.() -> T): T { | ||
return if (condition) | ||
block() | ||
else | ||
this | ||
error("Retries finished unexpectedly. Retries < 1?.") | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/main/kotlin/click/dobel/shelly/exporter/client/ShellyGen1Client.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package click.dobel.shelly.exporter.client | ||
|
||
import click.dobel.shelly.exporter.client.api.gen1.Settings | ||
import click.dobel.shelly.exporter.client.api.gen1.Shelly | ||
import click.dobel.shelly.exporter.client.api.gen1.Status | ||
import click.dobel.shelly.exporter.config.ShellyConfigProperties | ||
import mu.KLogging | ||
import org.springframework.boot.web.client.RestTemplateBuilder | ||
import org.springframework.cache.annotation.Cacheable | ||
import org.springframework.stereotype.Component | ||
|
||
@Component | ||
class ShellyGen1Client( | ||
configProperties: ShellyConfigProperties, | ||
restTemplateBuilder: RestTemplateBuilder | ||
) : ShellyClient(this) { | ||
companion object : KLogging() | ||
|
||
@Cacheable("Status", sync = true) | ||
fun status(address: String) = get<Status>(address, "status") | ||
|
||
@Cacheable("Shelly", sync = true) | ||
fun shelly(address: String) = get<Shelly>(address, "shelly") | ||
|
||
@Cacheable("Settings", sync = true) | ||
fun settings(address: String) = get<Settings>(address, "settings") | ||
|
||
override val restTemplate = restTemplateBuilder.createRestTemplate( | ||
configProperties.auth, | ||
configProperties.httpParams, | ||
) | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/kotlin/click/dobel/shelly/exporter/client/ShellyGen2Client.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package click.dobel.shelly.exporter.client | ||
|
||
import click.dobel.shelly.exporter.client.api.gen2.Gen2ShellyConfig | ||
import click.dobel.shelly.exporter.client.api.gen2.Gen2ShellyDeviceInfo | ||
import click.dobel.shelly.exporter.client.api.gen2.Gen2ShellyStatus | ||
import click.dobel.shelly.exporter.config.ShellyConfigProperties | ||
import mu.KLogging | ||
import org.springframework.boot.web.client.RestTemplateBuilder | ||
import org.springframework.cache.annotation.Cacheable | ||
import org.springframework.stereotype.Component | ||
|
||
|
||
@Component | ||
class ShellyGen2Client( | ||
configProperties: ShellyConfigProperties, | ||
restTemplateBuilder: RestTemplateBuilder | ||
) : ShellyClient(this) { | ||
|
||
companion object : KLogging() | ||
|
||
@Cacheable("Gen2ShellyStatus", sync = true) | ||
fun status(address: String) = get<Gen2ShellyStatus>(address, "rpc/Shelly.GetStatus") | ||
|
||
@Cacheable("Gen2ShellyDeviceInfo", sync = true) | ||
fun deviceInfo(address: String) = get<Gen2ShellyDeviceInfo>(address, "rpc/Shelly.GetDeviceInfo") | ||
|
||
@Cacheable("Gen2ShellyConfig", sync = true) | ||
fun config(address: String) = get<Gen2ShellyConfig>(address, "rpc/Shelly.GetConfig") | ||
|
||
override val restTemplate = restTemplateBuilder.createRestTemplate( | ||
configProperties.gen2auth, | ||
configProperties.httpParams, | ||
) | ||
} |
2 changes: 1 addition & 1 deletion
2
...el/shelly/exporter/client/api/Settings.kt → ...elly/exporter/client/api/gen1/Settings.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.