weather forecast
This commit is contained in:
parent
d24f0b9432
commit
8096c57952
|
|
@ -6,9 +6,12 @@ object GlobalDataStore {
|
||||||
@Volatile var temperature = 0.0
|
@Volatile var temperature = 0.0
|
||||||
@Volatile var tempUpdated = Date()
|
@Volatile var tempUpdated = Date()
|
||||||
@Volatile var isSunny = true
|
@Volatile var isSunny = true
|
||||||
|
@Volatile var resetTime = Date()
|
||||||
|
|
||||||
@Volatile var targetTemp = 22.5
|
@Volatile var targetTemp = 22.5
|
||||||
@Volatile var pval = 0.0
|
@Volatile var pval = 0.0
|
||||||
@Volatile var ival = 1.0
|
@Volatile var ival = 1.0
|
||||||
@Volatile var dval = 2.0
|
@Volatile var dval = 2.0
|
||||||
|
|
||||||
|
const val API = "https://api.open-meteo.com/v1/forecast?latitude=47.6649&longitude=14.3401&daily=sunshine_duration,daylight_duration&hourly=temperature_2m&timezone=Europe%2FBerlin&forecast_days=1&format=json&timeformat=unixtime"
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package dev.asdf00.visionfive.hc.control
|
||||||
|
|
||||||
|
class PidController {
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,11 @@ object LoginBehavior {
|
||||||
private val users = loadFromFile()
|
private val users = loadFromFile()
|
||||||
|
|
||||||
operator fun get(userName: String) = users[userName]
|
operator fun get(userName: String) = users[userName]
|
||||||
operator fun set(userName: String, password: String) {
|
operator fun set(userName: String, password: String?) {
|
||||||
|
if (password == null) {
|
||||||
|
users.remove(userName) == null
|
||||||
|
return
|
||||||
|
}
|
||||||
val salt = getRandomBase64(16)
|
val salt = getRandomBase64(16)
|
||||||
users[userName] = PwdHash(hashPassword(password, salt), salt)
|
users[userName] = PwdHash(hashPassword(password, salt), salt)
|
||||||
Files.writeString(FILE_PATH, users.map {
|
Files.writeString(FILE_PATH, users.map {
|
||||||
|
|
@ -36,8 +40,6 @@ object LoginBehavior {
|
||||||
return mutableMapOf()
|
return mutableMapOf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(userName: String) = users.remove(userName) == null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val loggedInUsers = mutableMapOf<String, String>()
|
private val loggedInUsers = mutableMapOf<String, String>()
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
package dev.asdf00.visionfive.hc.server
|
package dev.asdf00.visionfive.hc.server
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange
|
import com.sun.net.httpserver.HttpExchange
|
||||||
import dev.asdf00.visionfive.hc.GlobalDataStore
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
|
import dev.asdf00.visionfive.hc.GlobalDataStore
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.net.URI
|
||||||
|
import java.net.http.HttpClient
|
||||||
|
import java.net.http.HttpRequest
|
||||||
|
import java.net.http.HttpResponse
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
// out-facing web page
|
// out-facing web page
|
||||||
val server = buildWebServer(8030) {
|
val server = buildWebServer(8030) {
|
||||||
// main landing page
|
// main landing page
|
||||||
endpoint("") { exchange ->
|
endpoint("") { exchange ->
|
||||||
exchange.responseHeaders.add("Cache-Control", "no-store")
|
exchange.responseHeaders.add("Cache-Control", "no-store")
|
||||||
val token = getLoginToken(exchange)
|
val token = getAuthToken(exchange)
|
||||||
LoginBehavior.isLoggedIn(token)?.let { uname ->
|
LoginBehavior.isLoggedIn(token)?.let { uname ->
|
||||||
// logged in
|
// logged in
|
||||||
exchange.sendReply(
|
exchange.sendReply(
|
||||||
|
|
@ -113,7 +123,6 @@ fun main() {
|
||||||
}
|
}
|
||||||
LoginBehavior.login(userName, pwd)?.let {
|
LoginBehavior.login(userName, pwd)?.let {
|
||||||
exchange.responseHeaders.add("Set-Cookie", "auth=${it}; Path=/; HttpOnly; SameSite=Strict")
|
exchange.responseHeaders.add("Set-Cookie", "auth=${it}; Path=/; HttpOnly; SameSite=Strict")
|
||||||
exchange.responseHeaders.add("Set-Cookie", "test=2; Path=/; HttpOnly; SameSite=Strict")
|
|
||||||
exchange.sendReply(
|
exchange.sendReply(
|
||||||
200,
|
200,
|
||||||
ContentType.HTML,
|
ContentType.HTML,
|
||||||
|
|
@ -129,7 +138,7 @@ fun main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint("out") { exchange ->
|
endpoint("out") { exchange ->
|
||||||
val token = getLoginToken(exchange)
|
val token = getAuthToken(exchange)
|
||||||
if (LoginBehavior.logout(token)) {
|
if (LoginBehavior.logout(token)) {
|
||||||
exchange.responseHeaders.add("Set-Cookie", "auth=; Path=/")
|
exchange.responseHeaders.add("Set-Cookie", "auth=; Path=/")
|
||||||
exchange.sendReply(
|
exchange.sendReply(
|
||||||
|
|
@ -149,7 +158,7 @@ fun main() {
|
||||||
|
|
||||||
// pid control
|
// pid control
|
||||||
endpoint("pid") handler@{ exchange ->
|
endpoint("pid") handler@{ exchange ->
|
||||||
if (LoginBehavior.isLoggedIn(getLoginToken(exchange)) == null) {
|
if (LoginBehavior.isLoggedIn(getAuthToken(exchange)) == null) {
|
||||||
// not logged in
|
// not logged in
|
||||||
exchange.replyCat(401)
|
exchange.replyCat(401)
|
||||||
return@handler
|
return@handler
|
||||||
|
|
@ -158,7 +167,7 @@ fun main() {
|
||||||
// new values
|
// new values
|
||||||
try {
|
try {
|
||||||
val data = JSONObject(exchange.requestBody.readAllBytes().utf8()).toMap()
|
val data = JSONObject(exchange.requestBody.readAllBytes().utf8()).toMap()
|
||||||
if (!data.containsKey("temp") || !data.containsKey("p") || !data.containsKey("i") || ! data.containsKey("d")) {
|
if (!data.containsKey("temp") || !data.containsKey("p") || !data.containsKey("i") || !data.containsKey("d")) {
|
||||||
exchange.replyCat(400)
|
exchange.replyCat(400)
|
||||||
} else {
|
} else {
|
||||||
val temp = data["temp"].toString().toDouble()
|
val temp = data["temp"].toString().toDouble()
|
||||||
|
|
@ -176,7 +185,11 @@ fun main() {
|
||||||
}
|
}
|
||||||
} else if (exchange.requestMethod == "GET") {
|
} else if (exchange.requestMethod == "GET") {
|
||||||
// get current values
|
// get current values
|
||||||
exchange.sendReply(200, ContentType.JSON, "{\"temp\": ${GlobalDataStore.targetTemp}, \"p\": ${GlobalDataStore.pval}, \"i\": ${GlobalDataStore.ival}, \"d\": ${GlobalDataStore.dval}}".uft8())
|
exchange.sendReply(
|
||||||
|
200,
|
||||||
|
ContentType.JSON,
|
||||||
|
"{\"temp\": ${GlobalDataStore.targetTemp}, \"p\": ${GlobalDataStore.pval}, \"i\": ${GlobalDataStore.ival}, \"d\": ${GlobalDataStore.dval}}".uft8()
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
exchange.replyCat(400)
|
exchange.replyCat(400)
|
||||||
}
|
}
|
||||||
|
|
@ -202,6 +215,71 @@ fun main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val forecastQuery = Thread {
|
||||||
|
while (!Thread.interrupted()) {
|
||||||
|
var success = false
|
||||||
|
val request = HttpRequest.newBuilder(URI.create(GlobalDataStore.API)).GET().build()
|
||||||
|
HttpClient.newHttpClient()
|
||||||
|
.sendAsync(request, HttpResponse.BodyHandlers.ofString())
|
||||||
|
.thenApply { response ->
|
||||||
|
if (response.statusCode() == 200 && response.headers().firstValue("Content-Type")
|
||||||
|
.map { it.contains(ContentType.JSON.text) }.orElse(false)
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
val data = JSONObject(response.body())
|
||||||
|
var maxTemp = Double.NEGATIVE_INFINITY
|
||||||
|
var maxTempTime = -1
|
||||||
|
((data["hourly"] as? JSONObject)?.get("temperature_2m") as? JSONArray)?.forEachIndexed { h, t ->
|
||||||
|
(t as? BigDecimal)?.let {
|
||||||
|
val temp = it.toDouble()
|
||||||
|
if (maxTemp < temp) {
|
||||||
|
maxTemp = temp
|
||||||
|
maxTempTime = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (maxTempTime > 0) {
|
||||||
|
val daylight = ((data["daily"] as? JSONObject)?.get("daylight_duration") as? JSONArray)?.let {
|
||||||
|
if (it.length() == 1)
|
||||||
|
(it[0] as? BigDecimal)?.let { it.toDouble() }
|
||||||
|
else
|
||||||
|
null
|
||||||
|
} ?: 0.0
|
||||||
|
val sun = ((data["daily"] as? JSONObject)?.get("sunshine_duration") as? JSONArray)?.let {
|
||||||
|
if (it.length() == 1)
|
||||||
|
(it[0] as? BigDecimal)?.let { it.toDouble() }
|
||||||
|
else
|
||||||
|
null
|
||||||
|
} ?: 0.0
|
||||||
|
|
||||||
|
GlobalDataStore.resetTime = Calendar.getInstance().also {
|
||||||
|
it.set(Calendar.HOUR_OF_DAY, maxTempTime)
|
||||||
|
it.set(Calendar.MINUTE, 30)
|
||||||
|
it.set(Calendar.SECOND, 0)
|
||||||
|
}.time
|
||||||
|
GlobalDataStore.isSunny = sun > daylight / 2
|
||||||
|
success = true
|
||||||
|
} else {
|
||||||
|
println("error reading weather API response")
|
||||||
|
}
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
println("error parsing weather API response")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("error querying weather API response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.join()
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
Thread.sleep(Duration.ofHours(12))
|
||||||
|
} else {
|
||||||
|
Thread.sleep(Duration.ofMinutes(30))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
forecastQuery.start()
|
||||||
server.start()
|
server.start()
|
||||||
tempServer.start()
|
tempServer.start()
|
||||||
println("HttpServer is running, press CTRL+C to exit")
|
println("HttpServer is running, press CTRL+C to exit")
|
||||||
|
|
@ -213,13 +291,14 @@ fun main() {
|
||||||
val pwd = readln()
|
val pwd = readln()
|
||||||
LoginBehavior.Users[userName] = pwd
|
LoginBehavior.Users[userName] = pwd
|
||||||
}
|
}
|
||||||
|
forecastQuery.interrupt()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.uft8() = toByteArray(StandardCharsets.UTF_8)
|
fun String.uft8() = toByteArray(StandardCharsets.UTF_8)
|
||||||
|
|
||||||
fun ByteArray.utf8() = String(this, StandardCharsets.UTF_8)
|
fun ByteArray.utf8() = String(this, StandardCharsets.UTF_8)
|
||||||
|
|
||||||
private fun getLoginToken(exchange: HttpExchange) =
|
private fun getAuthToken(exchange: HttpExchange) =
|
||||||
exchange.requestHeaders["Cookie"]
|
exchange.requestHeaders["Cookie"]
|
||||||
?.let { if (it.size > 0) it[0] else null }
|
?.let { if (it.size > 0) it[0] else null }
|
||||||
?.let { it.split("; ") }
|
?.let { it.split("; ") }
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user