diff --git a/buildSrc/src/main/kotlin/org.openbase.bco.gradle.kts b/buildSrc/src/main/kotlin/org.openbase.bco.gradle.kts index 32f60532f5..681c515cba 100644 --- a/buildSrc/src/main/kotlin/org.openbase.bco.gradle.kts +++ b/buildSrc/src/main/kotlin/org.openbase.bco.gradle.kts @@ -1,12 +1,12 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.util.Base64 +import java.util.* plugins { `java-library` `maven-publish` kotlin("jvm") signing - id ("com.adarshr.test-logger") + id("com.adarshr.test-logger") } repositories { @@ -31,15 +31,15 @@ java { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-reflect:1.7.0") - api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.0") - implementation("org.jetbrains.kotlin:kotlin-script-runtime:1.5.21") + implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.20") + api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.20") + implementation("org.jetbrains.kotlin:kotlin-script-runtime:1.9.20") testImplementation("org.junit.jupiter:junit-jupiter:[5.8,5.9-alpha)") - testImplementation ("org.junit.jupiter:junit-jupiter-api:[5.8,5.9-alpha)") + testImplementation("org.junit.jupiter:junit-jupiter-api:[5.8,5.9-alpha)") testImplementation(Testing.mockK) testImplementation("io.quarkus:quarkus-junit4-mock:_") testImplementation("io.kotest:kotest-assertions-core-jvm:_") - testRuntimeOnly ("org.junit.jupiter:junit-jupiter-engine:[5.8,5.9-alpha)") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:[5.8,5.9-alpha)") } tasks.withType { @@ -123,7 +123,7 @@ signing { ?.let { it as String? } ?.let { Base64.getDecoder().decode(it) } ?.let { String(it) } - ?:run { + ?: run { // Signing skipped because of missing private key. return@signing } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa7..033e24c4cd 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 17a8ddce2d..b82aa23a4f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d65..fcb6fca147 100755 --- a/gradlew +++ b/gradlew @@ -85,9 +85,6 @@ done APP_BASE_NAME=${0##*/} APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +130,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +144,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +152,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,6 +197,10 @@ if "$cygwin" || "$msys" ; then done fi + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in diff --git a/lib/jul b/lib/jul index 57f9689153..2c6954c7c0 160000 --- a/lib/jul +++ b/lib/jul @@ -1 +1 @@ -Subproject commit 57f9689153d476d593e2fc9cfaad980cddbe67d5 +Subproject commit 2c6954c7c058245c6a10256f42aa04549b1f9704 diff --git a/module/app/influxdbconnector/src/main/java/org/openbase/bco/app/influxdbconnector/InfluxDbconnectorApp.kt b/module/app/influxdbconnector/src/main/java/org/openbase/bco/app/influxdbconnector/InfluxDbconnectorApp.kt index c4e1923a1a..0e5a72ce68 100644 --- a/module/app/influxdbconnector/src/main/java/org/openbase/bco/app/influxdbconnector/InfluxDbconnectorApp.kt +++ b/module/app/influxdbconnector/src/main/java/org/openbase/bco/app/influxdbconnector/InfluxDbconnectorApp.kt @@ -32,7 +32,7 @@ import org.openbase.type.domotic.service.ServiceTemplateType import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServicePattern import org.openbase.type.domotic.service.ServiceTempusTypeType.ServiceTempusType.ServiceTempus import org.openbase.type.domotic.state.ActivationStateType -import org.openbase.type.domotic.unit.UnitConfigType +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig import java.util.* import java.util.concurrent.* import java.util.concurrent.TimeoutException @@ -45,15 +45,14 @@ class InfluxDbconnectorApp : AbstractAppController() { private var token: CharArray? = null private var task: Future<*>? = null private var heartbeat: Future<*>? = null - private var databaseUrl: String? = null - private var bucketName: String? = null + private var databaseUrl: String? = InfluxDbProcessor.INFLUXDB_URL_DEFAULT + private var bucketName: String = InfluxDbProcessor.INFLUXDB_BUCKET_DEFAULT private var influxDBClient: InfluxDBClient? = null - private var batchTime: Int? = null - private var batchLimit: Int? = null + private var batchTime: Int = InfluxDbProcessor.INFLUXDB_BATCH_TIME_DEFAULT + private var batchLimit: Int = InfluxDbProcessor.INFLUXDB_BATCH_LIMIT_DEFAULT private val customUnitPool: CustomUnitPool<*, *> = CustomUnitPool>() private val unitStateObserver: Observer, Message> - private var org: String? = null - + private var org: String = InfluxDbProcessor.INFLUXDB_ORG_DEFAULT init { this.unitStateObserver = @@ -67,28 +66,31 @@ class InfluxDbconnectorApp : AbstractAppController() { } @Throws(CouldNotPerformException::class, InterruptedException::class) - override fun applyConfigUpdate(config: UnitConfigType.UnitConfig): UnitConfigType.UnitConfig { - var config: UnitConfigType.UnitConfig? = config - getManageWriteLockInterruptible(this).use { ignored -> - config = super.applyConfigUpdate(config!!) - bucketName = generateVariablePool().getValue( - InfluxDbProcessor.INFLUXDB_BUCKET, - InfluxDbProcessor.INFLUXDB_BUCKET_DEFAULT - ) - batchTime = generateVariablePool().getValue( - InfluxDbProcessor.INFLUXDB_BATCH_TIME, - InfluxDbProcessor.INFLUXDB_BATCH_TIME_DEFAULT - ).toInt() - batchLimit = generateVariablePool().getValue( - InfluxDbProcessor.INFLUXDB_BATCH_LIMIT, - InfluxDbProcessor.INFLUXDB_BATCH_LIMIT_DEFAULT - ).toInt() - databaseUrl = - generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_URL, InfluxDbProcessor.INFLUXDB_URL_DEFAULT) - token = generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_TOKEN).toCharArray() - org = - generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_ORG, InfluxDbProcessor.INFLUXDB_ORG_DEFAULT) - return config!! + override fun applyConfigUpdate(config: UnitConfig): UnitConfig { + getManageWriteLockInterruptible(this).use { _ -> + return super.applyConfigUpdate(config).also { + bucketName = tryOrNull { + generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_BUCKET) + } ?: bucketName + + batchTime = tryOrNull { + generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_BATCH_TIME).toInt() + } ?: batchTime + + batchLimit = tryOrNull { + generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_BATCH_LIMIT).toInt() + } ?: batchLimit + + databaseUrl = tryOrNull { + generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_URL) + } ?: databaseUrl + + org = tryOrNull { + generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_ORG) + } ?: org + + token = generateVariablePool().getValue(InfluxDbProcessor.INFLUXDB_TOKEN).toCharArray() + } } } @@ -99,7 +101,7 @@ class InfluxDbconnectorApp : AbstractAppController() { // connect to db connectToDatabase() - while (!task!!.isCancelled) { + while (task?.isCancelled == false) { try { verifyConnection() break @@ -111,12 +113,14 @@ class InfluxDbconnectorApp : AbstractAppController() { LogLevel.WARN ) Thread.sleep(databaseTimeout.toLong()) - if (databaseTimeout < InfluxDbProcessor.MAX_TIMEOUT) databaseTimeout += InfluxDbProcessor.ADDITIONAL_TIMEOUT + if (databaseTimeout < InfluxDbProcessor.MAX_TIMEOUT) { + databaseTimeout += InfluxDbProcessor.ADDITIONAL_TIMEOUT + } } } // lookup bucked - while (!task!!.isCancelled) { + while (task?.isCancelled == false) { try { // check if bucked found databaseBucket @@ -137,18 +141,20 @@ class InfluxDbconnectorApp : AbstractAppController() { } } catch (ex: InterruptedException) { // finish task because its canceled. + Thread.currentThread().interrupt() + return@submit } - if (!task!!.isCancelled && isConnected) { + if (task?.isCancelled == false && isConnected) { try { // write initial heartbeat logger.debug("initial heartbeat") - writeApi!!.writePoint( - bucketName!!, org!!, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) + writeApi?.writePoint( + bucketName, org, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) .addField(InfluxDbProcessor.HEARTBEAT_FIELD, InfluxDbProcessor.HEARTBEAT_OFFLINE_VALUE) .time(System.currentTimeMillis() - 1, WritePrecision.MS) ) - writeApi!!.writePoint( - bucketName!!, org!!, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) + writeApi?.writePoint( + bucketName, org, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) .addField(InfluxDbProcessor.HEARTBEAT_FIELD, InfluxDbProcessor.HEARTBEAT_ONLINE_VALUE) .time(System.currentTimeMillis(), WritePrecision.MS) ) @@ -156,8 +162,8 @@ class InfluxDbconnectorApp : AbstractAppController() { heartbeat = GlobalScheduledExecutorService.scheduleAtFixedRate( { logger.debug("write heartbeat") - writeApi!!.writePoint( - bucketName!!, org!!, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) + writeApi?.writePoint( + bucketName, org, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) .addField( InfluxDbProcessor.HEARTBEAT_FIELD, InfluxDbProcessor.HEARTBEAT_ONLINE_VALUE @@ -170,7 +176,7 @@ class InfluxDbconnectorApp : AbstractAppController() { TimeUnit.MILLISECONDS ) } catch (ex: NotAvailableException) { - ExceptionPrinter.printHistory( + ExceptionPrinter.printHistory( "Could not write heartbeat!", ex, logger, @@ -188,10 +194,10 @@ class InfluxDbconnectorApp : AbstractAppController() { // finish task logger.debug("finish task") - if (task != null && !task!!.isDone) { - task!!.cancel(true) + task?.takeIf { !it.isDone }?.let { task -> + task.cancel(true) try { - task!![5, TimeUnit.SECONDS] + task[5, TimeUnit.SECONDS] } catch (ex: CancellationException) { // that's what we are waiting for. } catch (ex: Exception) { @@ -200,10 +206,11 @@ class InfluxDbconnectorApp : AbstractAppController() { } logger.debug("finish heartbeat") - if (heartbeat != null && !heartbeat!!.isDone) { - heartbeat!!.cancel(true) + + heartbeat?.takeIf { !it.isDone }?.let { heartbeat -> + heartbeat.cancel(true) try { - task!![5, TimeUnit.SECONDS] + heartbeat[5, TimeUnit.SECONDS] } catch (ex: CancellationException) { // that's what we are waiting for. } catch (ex: Exception) { @@ -212,18 +219,18 @@ class InfluxDbconnectorApp : AbstractAppController() { } if (isConnected) { - // write final heartbeat if connection is established. - writeApi!!.writePoint( - bucketName!!, org!!, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) + // write final heartbeat if connection is still established. + writeApi?.writePoint( + bucketName, org, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) .addField(InfluxDbProcessor.HEARTBEAT_FIELD, InfluxDbProcessor.HEARTBEAT_ONLINE_VALUE) .time(System.currentTimeMillis() - 1, WritePrecision.MS) ) - writeApi!!.writePoint( - bucketName!!, org!!, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) + writeApi?.writePoint( + bucketName, org, Point.measurement(InfluxDbProcessor.HEARTBEAT_MEASUREMENT) .addField(InfluxDbProcessor.HEARTBEAT_FIELD, InfluxDbProcessor.HEARTBEAT_OFFLINE_VALUE) .time(System.currentTimeMillis(), WritePrecision.MS) ) - writeApi!!.flush() + writeApi?.flush() } // deregister @@ -337,7 +344,7 @@ class InfluxDbconnectorApp : AbstractAppController() { return } - ExceptionPrinter.printHistory( + ExceptionPrinter.printHistory( "Could not store service state change into db! " + "UnitType[" + unit.unitType + "] " + "ServiceType[" + serviceType + "] " + @@ -352,19 +359,14 @@ class InfluxDbconnectorApp : AbstractAppController() { private fun storeServiceState( unit: org.openbase.bco.dal.lib.layer.unit.Unit<*>, serviceType: ServiceTemplateType.ServiceTemplate.ServiceType, - serviceState: Message?, + serviceState: Message, ) { val timestamp = TimestampProcessor.getTimestamp(serviceState, TimeUnit.MILLISECONDS) try { - var initiator = try { - Services.getResponsibleAction(serviceState).getActionInitiator() - .getInitiatorType().name.lowercase( - Locale.getDefault() - ) - } catch (ex: NotAvailableException) { - // in this case we use the system as initiator because responsible actions are not available for pure provider services and those are always system generated. - "system" - } + val initiator = runCatching { + Services.getResponsibleAction(serviceState).actionInitiator.initiatorType.name.lowercase() + }.getOrElse { "system" } + val stateValuesMap = resolveStateValueToMap(serviceState) val point = Point.measurement(serviceType.name.lowercase(Locale.getDefault())) .addTag("alias", unit.config.getAlias(0)) @@ -386,17 +388,14 @@ class InfluxDbconnectorApp : AbstractAppController() { } } - val entryList = unit.config.label.entryList - for (entry in entryList) { - // skip entries that offer a language key but do not provide any label. - if (entry.valueList.isEmpty()) { - continue - } - point.addTag("label_" + entry.key, entry.getValue(0)) - } + unit.config.label.entryList + .filter { it.valueList.isNotEmpty() } + .forEach { point.addTag("label_" + it.key, it.getValue(0)) } if (values > 0) { - writeApi!!.writePoint(bucketName!!, org!!, point) + org.let { org -> + writeApi?.writePoint(bucketName, org, point) + } } } catch (ex: CouldNotPerformException) { ExceptionPrinter.printHistory( @@ -409,11 +408,11 @@ class InfluxDbconnectorApp : AbstractAppController() { @Throws(CouldNotPerformException::class) - fun resolveStateValueToMap(serviceState: Message?): Map { + fun resolveStateValueToMap(serviceState: Message): Map { val stateValues: MutableMap = HashMap() - for (fieldDescriptor in serviceState!!.descriptorForType.fields) { + for (fieldDescriptor in serviceState.descriptorForType.fields) { val stateName = fieldDescriptor.name - var stateType = fieldDescriptor.type.toString().lowercase(Locale.getDefault()) + var stateType = fieldDescriptor.type.toString().lowercase() // filter invalid states if (stateName == null) { @@ -424,12 +423,17 @@ class InfluxDbconnectorApp : AbstractAppController() { "aggregated_value_coverage", "last_value_occurrence", "timestamp", "responsible_action", "type", "rgb_color", "frame_id" -> continue } // filter data units - if (stateName!!.endsWith("data_unit")) { + if (stateName.endsWith("data_unit")) { continue } var stateValue = serviceState.getField(fieldDescriptor).toString() + when (stateValue) { + "", "NaN" -> continue + else -> {} + } + try { if (fieldDescriptor.type == Descriptors.FieldDescriptor.Type.MESSAGE) { if (fieldDescriptor.isRepeated) { @@ -458,10 +462,6 @@ class InfluxDbconnectorApp : AbstractAppController() { continue } - when (stateValue) { - "", "NaN" -> continue - else -> {} - } if (fieldDescriptor.javaType == Descriptors.FieldDescriptor.JavaType.ENUM) { val finalStateValue = stateValue stateValue = fieldDescriptor.enumType.values.stream() @@ -469,7 +469,7 @@ class InfluxDbconnectorApp : AbstractAppController() { .findFirst().get().number.toString() } - stateValues[fieldDescriptor.name] = stateValue.lowercase(Locale.getDefault()) + stateValues[fieldDescriptor.name] = stateValue.lowercase() } return stateValues } @@ -500,17 +500,17 @@ class InfluxDbconnectorApp : AbstractAppController() { throw VerificationFailedException("Influx db connection has never been initiated.") } - if (influxDBClient!!.health().status.value !== "pass") { + if (influxDBClient?.ping() != true) { throw VerificationFailedException("Could not connect to database server at $databaseUrl!") } // initiate WriteApi - val writeoptions = WriteOptions.builder().batchSize(batchLimit!!).flushInterval(batchTime!!).build() - writeApi = influxDBClient!!.getWriteApi(writeoptions) - writeApi!!.listenEvents(WriteSuccessEvent::class.java) { event: WriteSuccessEvent? -> + val writeoptions = WriteOptions.builder().batchSize(batchLimit).flushInterval(batchTime).build() + writeApi = influxDBClient?.makeWriteApi(writeoptions) + writeApi?.listenEvents(WriteSuccessEvent::class.java) { logger.debug("Successfully wrote data into db") } - writeApi!!.listenEvents(WriteErrorEvent::class.java) { event: WriteErrorEvent -> + writeApi?.listenEvents(WriteErrorEvent::class.java) { event: WriteErrorEvent -> val exception = event.throwable logger.warn(exception.message) } @@ -525,7 +525,6 @@ class InfluxDbconnectorApp : AbstractAppController() { } logger.debug(" Try to connect to influxDB at $databaseUrl") - token?.let { influxDBClient = InfluxDBClientFactory.create( databaseUrl + "?readTimeout=" + InfluxDbProcessor.READ_TIMEOUT + "&connectTimeout=" + InfluxDbProcessor.CONNECT_TIMOUT + "&writeTimeout=" + InfluxDbProcessor.WRITE_TIMEOUT + "&logLevel=BASIC", @@ -536,13 +535,9 @@ class InfluxDbconnectorApp : AbstractAppController() { private fun disconnectDatabase() { try { - if (influxDBClient != null) { - if (writeApi != null) { - writeApi!!.flush() - } - influxDBClient!!.close() - writeApi = null - } + writeApi?.flush() + influxDBClient?.close() + writeApi = null } catch (ex: Exception) { ExceptionPrinter.printHistory("Could not shutdown database connection!", ex, logger) } @@ -552,7 +547,7 @@ class InfluxDbconnectorApp : AbstractAppController() { private val databaseBucket: Unit get() { logger.debug("Get bucket $bucketName") - bucket = influxDBClient!!.bucketsApi.findBucketByName(bucketName!!) + bucket = influxDBClient?.bucketsApi?.findBucketByName(bucketName) if (bucket == null) { throw NotAvailableException("bucket", bucketName) } diff --git a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt index 7c1a8e4b0e..86262355d2 100644 --- a/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt +++ b/module/app/preset/src/main/java/org/openbase/bco/app/preset/DeviceNotificationApp.kt @@ -103,7 +103,7 @@ class DeviceNotificationApp : AbstractAppController() { messageBuilder.id = UUID.randomUUID().toString() messageBuilder.messageType = UserMessage.MessageType.WARNING - messageBuilder.timestamp = TimestampProcessor.getCurrentTimestamp() + messageBuilder.timestamp = TimestampProcessor.currentTimestamp messageBuilder.text = textBuilder.build() messageBuilder.senderId = userConfig.id messageBuilder.addCondition( @@ -141,7 +141,7 @@ class DeviceNotificationApp : AbstractAppController() { companion object { private val LOGGER = LoggerFactory.getLogger(TemplateApp::class.java) - + private val VALIDATION_PERIOD: Duration = Duration.ofHours(24) private val INITIAL_VALIDATION_DELAY: Duration = Duration.ofHours(1) } diff --git a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java index 93f10865dd..2eaaea1fbd 100644 --- a/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java +++ b/module/dal/control/src/main/java/org/openbase/bco/dal/control/layer/unit/InfluxDbProcessor.java @@ -74,9 +74,9 @@ public class InfluxDbProcessor { public static final String INFLUXDB_BUCKET = "INFLUXDB_BUCKET"; public static final String INFLUXDB_BUCKET_DEFAULT = "bco-persistence"; public static final String INFLUXDB_BATCH_TIME = "INFLUXDB_BATCH_TIME"; - public static final String INFLUXDB_BATCH_TIME_DEFAULT = "1000"; + public static final Integer INFLUXDB_BATCH_TIME_DEFAULT = 1000; public static final String INFLUXDB_BATCH_LIMIT = "INFLUXDB_BATCH_LIMIT"; - public static final String INFLUXDB_BATCH_LIMIT_DEFAULT = "100"; + public static final Integer INFLUXDB_BATCH_LIMIT_DEFAULT = 100; public static final String INFLUXDB_URL = "INFLUXDB_URL"; public static final String INFLUXDB_URL_DEFAULT = "http://influxdb:8086"; public static final String INFLUXDB_ORG = "INFLUXDB_ORG"; @@ -125,8 +125,8 @@ private static void updateConfiguration() { influxDbBucket = metaConfigPool.getValue(INFLUXDB_BUCKET, INFLUXDB_BUCKET_DEFAULT); influxDbUrl = metaConfigPool.getValue(INFLUXDB_URL, INFLUXDB_URL_DEFAULT); influxDbOrg = metaConfigPool.getValue(INFLUXDB_ORG, INFLUXDB_ORG_DEFAULT); - influxDbBatchTime = metaConfigPool.getValue(INFLUXDB_BATCH_TIME, INFLUXDB_BATCH_TIME_DEFAULT); - influxDbBatchLimit = metaConfigPool.getValue(INFLUXDB_BATCH_LIMIT, INFLUXDB_BATCH_LIMIT_DEFAULT); + influxDbBatchTime = metaConfigPool.getValue(INFLUXDB_BATCH_TIME, INFLUXDB_BATCH_TIME_DEFAULT.toString()); + influxDbBatchLimit = metaConfigPool.getValue(INFLUXDB_BATCH_LIMIT, INFLUXDB_BATCH_LIMIT_DEFAULT.toString()); try { influxDbToken = metaConfigPool.getValue(INFLUXDB_TOKEN).toCharArray(); diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java deleted file mode 100644 index 9fc1aa24ba..0000000000 --- a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.java +++ /dev/null @@ -1,373 +0,0 @@ -package org.openbase.bco.dal.remote.detector; - -/* - * #%L - * BCO DAL Remote - * %% - * Copyright (C) 2014 - 2021 openbase.org - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Lesser Public License for more details. - * - * You should have received a copy of the GNU General Lesser Public - * License along with this program. If not, see - * . - * #L% - */ - -import org.openbase.bco.dal.lib.layer.unit.location.Location; -import org.openbase.bco.dal.remote.layer.unit.CustomUnitPool; -import org.openbase.jps.core.JPService; -import org.openbase.jul.exception.InstantiationException; -import org.openbase.jul.exception.*; -import org.openbase.jul.exception.printer.ExceptionPrinter; -import org.openbase.jul.exception.printer.LogLevel; -import org.openbase.jul.extension.type.processing.TimestampProcessor; -import org.openbase.jul.iface.Manageable; -import org.openbase.jul.pattern.ObservableImpl; -import org.openbase.jul.pattern.Observer; -import org.openbase.jul.pattern.provider.DataProvider; -import org.openbase.jul.schedule.GlobalCachedExecutorService; -import org.openbase.jul.schedule.Timeout; -import org.openbase.type.domotic.state.ButtonStateType.ButtonState; -import org.openbase.type.domotic.state.ButtonStateType.ButtonStateOrBuilder; -import org.openbase.type.domotic.state.DoorStateType.DoorState; -import org.openbase.type.domotic.state.MotionStateType.MotionState; -import org.openbase.type.domotic.state.MotionStateType.MotionStateOrBuilder; -import org.openbase.type.domotic.state.PresenceStateType.PresenceState; -import org.openbase.type.domotic.state.PresenceStateType.PresenceState.State; -import org.openbase.type.domotic.state.PresenceStateType.PresenceStateOrBuilder; -import org.openbase.type.domotic.state.WindowStateType.WindowState; -import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType; -import org.openbase.type.domotic.unit.connection.ConnectionConfigType.ConnectionConfig.ConnectionType; -import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType; -import org.openbase.type.domotic.unit.location.LocationDataType.LocationData; -import org.openbase.type.domotic.unit.location.TileConfigType.TileConfig.TileType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -/** - * @author Divine Threepwood - */ -public class PresenceDetector implements Manageable, DataProvider { - - /** - * Default 3 minute window of no movement unit the state switches to - * NO_MOTION. - */ - public static final long PRESENCE_TIMEOUT = JPService.testMode() ? 50 : 60000; - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - private final PresenceState.Builder presenceStateBuilder; - private final Timeout presenceTimeout; - private final Observer, LocationData> locationDataObserver; - private Location location; - private final ObservableImpl, PresenceState> presenceStateObservable; - private final CustomUnitPool buttonUnitPool; - private final CustomUnitPool connectionUnitPool; - - private boolean active; - private boolean shutdownInitiated = false; - - public PresenceDetector() throws InstantiationException { - this.presenceStateBuilder = PresenceState.newBuilder(); - this.active = false; - this.presenceStateObservable = new ObservableImpl<>(this); - this.presenceTimeout = new Timeout(PRESENCE_TIMEOUT) { - - @Override - public void expired() { - try { - // if motion is still detected just restart the timeout. - if (location.getData().getMotionState().getValue() == MotionState.State.MOTION) { - GlobalCachedExecutorService.submit(() -> { - try { - presenceTimeout.restart(); - } catch (final CouldNotPerformException ex) { - ExceptionPrinter.printHistory("Could not setup presence timeout!", ex, logger); - } - }); - return; - } - updatePresenceState(PresenceState.newBuilder().setValue(PresenceState.State.ABSENT)); - } catch (ShutdownInProgressException ex) { - // skip update on shutdown - } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory(new CouldNotPerformException("Could not notify absent by timer!", ex), logger); - } - } - }; - - locationDataObserver = (DataProvider source, LocationData data) -> { - updateMotionState(data.getMotionState()); - }; - - this.buttonUnitPool = new CustomUnitPool(); - this.connectionUnitPool = new CustomUnitPool(); - - this.buttonUnitPool.addServiceStateObserver((source, data) -> { - try { - PresenceDetector.this.updateButtonState((ButtonState) data); - } catch (ClassCastException ex) { - ExceptionPrinter.printHistory("ButtonPool entail incompatible units!", ex, logger); - } - }); - - this.connectionUnitPool.addServiceStateObserver((source, data) -> { - switch (source.getServiceType()) { - case WINDOW_STATE_SERVICE: - updateWindowState((WindowState) data); - break; - case DOOR_STATE_SERVICE: - updateDoorState((DoorState) data); - break; - case PASSAGE_STATE_SERVICE: - // just ignore passage states. - break; - - default: - logger.warn("Invalid connection service update received: " + source.getServiceType().name() + " from " + source + " pool:" + connectionUnitPool.isActive()); - } - }); - } - - @Override - public void init(final Location location) throws InitializationException, InterruptedException { - try { - this.location = location; - buttonUnitPool.init( - unitConfig -> unitConfig.getUnitType() == UnitType.BUTTON, - unitConfig -> { - try { - return unitConfig.getPlacementConfig().getLocationId().equals(location.getId()); - } catch (NotAvailableException ex) { - ExceptionPrinter.printHistory("Could not resolve location id within button filter operation.", ex, logger); - return true; - } - }); - - - if ((location.getConfig().getLocationConfig().getLocationType() == LocationType.TILE)) { - connectionUnitPool.init( - unitConfig -> unitConfig.getUnitType() == UnitType.CONNECTION, - unitConfig -> { - try { - return unitConfig.getConnectionConfig().getTileIdList().contains(location.getId()); - } catch (NotAvailableException ex) { - ExceptionPrinter.printHistory("Could not resolve location id within connection filter operation.", ex, logger); - return false; - } - }, - unitConfig -> { - try { - return location.getConfig().getLocationConfig().getTileConfig().getTileType() != TileType.OUTDOOR || unitConfig.getConnectionConfig().getConnectionType() != ConnectionType.WINDOW; - } catch (NotAvailableException ex) { - ExceptionPrinter.printHistory("Could not resolve location id within connection filter operation.", ex, logger); - return false; - } - }); - } - } catch (CouldNotPerformException ex) { - throw new InitializationException(this, ex); - } - } - - public void init(final Location location, final long motionTimeout) throws InitializationException, InterruptedException { - init(location); - presenceTimeout.setDefaultWaitTime(motionTimeout); - } - - @Override - public void activate() throws CouldNotPerformException, InterruptedException { - - if (locationDataObserver == null) { - throw new NotInitializedException(this); - } - active = true; - location.addDataObserver(locationDataObserver); - - buttonUnitPool.activate(); - - if ((location.getConfig().getLocationConfig().getLocationType() == LocationType.TILE)) { - connectionUnitPool.activate(); - } - - // start initial timeout - presenceTimeout.start(); - updateMotionState(location.getData().getMotionState()); - } - - - @Override - public void deactivate() throws CouldNotPerformException, InterruptedException { - active = false; - presenceTimeout.cancel(); - if (location != null) { - // can be null if never initialized or initialization failed - location.removeDataObserver(locationDataObserver); - } - buttonUnitPool.deactivate(); - if ((location.getConfig().getLocationConfig().getLocationType() == LocationType.TILE)) { - connectionUnitPool.deactivate(); - } - } - - @Override - public boolean isActive() { - return active; - } - - @Override - public void validateData() throws InvalidStateException { - if (isShutdownInitiated()) { - throw new InvalidStateException(new ShutdownInProgressException(this)); - } - - if (isDataAvailable()) { - throw new InvalidStateException(new NotAvailableException("Data")); - } - } - - public boolean isShutdownInitiated() { - return shutdownInitiated; - } - - @Override - public void shutdown() { - try { - shutdownInitiated = true; - deactivate(); - } catch (CouldNotPerformException | InterruptedException ex) { - ExceptionPrinter.printHistory(ex, logger); - } - buttonUnitPool.shutdown(); - connectionUnitPool.shutdown(); - } - - private synchronized void updatePresenceState(final PresenceStateOrBuilder presenceState) throws CouldNotPerformException { - - // update timestamp and reset timer - if (presenceState.getValue() == PresenceState.State.PRESENT && presenceStateBuilder.getTimestamp().getTime() != presenceState.getTimestamp().getTime()) { - presenceTimeout.restart(); - presenceStateBuilder.getTimestampBuilder().setTime(Math.max(presenceStateBuilder.getTimestamp().getTime(), presenceState.getTimestamp().getTime())); - } - - // filter non state changes - if (presenceStateBuilder.getValue() == presenceState.getValue()) { - return; - } - - // update value - TimestampProcessor.updateTimestampWithCurrentTime(presenceStateBuilder, logger); - presenceStateBuilder.setValue(presenceState.getValue()); - - // notify - try { - presenceStateObservable.notifyObservers(this.presenceStateBuilder.build()); - } catch (CouldNotPerformException ex) { - ExceptionPrinter.printHistory(new CouldNotPerformException("Could not update MotionState!", ex), logger, LogLevel.ERROR); - } - } - - private void updateMotionState(final MotionStateOrBuilder motionState) throws CouldNotPerformException { - - // Filter rush motion predictions. - if (motionState.getValue() == MotionState.State.NO_MOTION) { - return; - } - - if (motionState.getValue() == MotionState.State.MOTION) { - updatePresenceState(TimestampProcessor.updateTimestampWithCurrentTime(PresenceState.newBuilder().setValue(State.PRESENT).setResponsibleAction(motionState.getResponsibleAction()).build())); - } - } - - private void updateButtonState(final ButtonStateOrBuilder buttonState) throws CouldNotPerformException { - switch (buttonState.getValue()) { - case PRESSED: - case RELEASED: - case DOUBLE_PRESSED: - // note: currently disabled because some hardware setups offer a fix link between lights and buttons. Therefore, any light actions are resulting in a PRESENT state which then triggers further actions. - // updatePresenceState(TimestampProcessor.updateTimestampWithCurrentTime(PresenceState.newBuilder().setValue(State.PRESENT).setResponsibleAction(buttonState.getResponsibleAction()).build())); - case UNKNOWN: - default: - // ignore non presence prove - return; - } - } - - private void updateDoorState(final DoorState doorState) throws CouldNotPerformException { - switch (doorState.getValue()) { - case OPEN: - updatePresenceState(TimestampProcessor.updateTimestampWithCurrentTime(PresenceState.newBuilder().setValue(State.PRESENT).setResponsibleAction(doorState.getResponsibleAction()).build())); - case CLOSED: - case UNKNOWN: - default: - // ignore non presence prove - return; - } - } - - private void updateWindowState(final WindowState windowState) throws CouldNotPerformException { - switch (windowState.getValue()) { - case OPEN: - case TILTED: - case CLOSED: - updatePresenceState(TimestampProcessor.updateTimestampWithCurrentTime(PresenceState.newBuilder().setValue(State.PRESENT).setResponsibleAction(windowState.getResponsibleAction()).build())); - case UNKNOWN: - default: - // ignore non presence prove - return; - } - } - - @Override - public boolean isDataAvailable() { - return presenceStateObservable.isValueAvailable(); - } - - @Override - public Class getDataClass() { - return PresenceState.class; - } - - @Override - public PresenceState getData() throws NotAvailableException { - return presenceStateObservable.getValue(); - } - - @Override - public Future getDataFuture() { - return presenceStateObservable.getValueFuture(); - } - - @Override - public void addDataObserver(final Observer, PresenceState> observer) { - presenceStateObservable.addObserver(observer); - } - - @Override - public void removeDataObserver(final Observer, PresenceState> observer) { - presenceStateObservable.removeObserver(observer); - } - - @Override - public void waitForData() throws CouldNotPerformException, InterruptedException { - presenceStateObservable.waitForValue(); - } - - @Override - public void waitForData(final long timeout, final TimeUnit timeUnit) throws CouldNotPerformException, InterruptedException { - presenceStateObservable.waitForValue(timeout, timeUnit); - } -} diff --git a/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.kt b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.kt new file mode 100644 index 0000000000..dd0c6f6484 --- /dev/null +++ b/module/dal/remote/src/main/java/org/openbase/bco/dal/remote/detector/PresenceDetector.kt @@ -0,0 +1,408 @@ +package org.openbase.bco.dal.remote.detector + +import com.google.protobuf.Message +import org.openbase.bco.dal.lib.layer.service.ServiceStateProcessor +import org.openbase.bco.dal.lib.layer.service.ServiceStateProvider +import org.openbase.bco.dal.lib.layer.unit.UnitRemote +import org.openbase.bco.dal.lib.layer.unit.location.Location +import org.openbase.bco.dal.remote.layer.unit.CustomUnitPool +import org.openbase.jps.core.JPService +import org.openbase.jul.exception.* +import org.openbase.jul.exception.printer.ExceptionPrinter +import org.openbase.jul.exception.printer.LogLevel +import org.openbase.jul.extension.type.processing.TimestampProcessor +import org.openbase.jul.extension.type.processing.instant +import org.openbase.jul.iface.Manageable +import org.openbase.jul.pattern.Filter +import org.openbase.jul.pattern.ObservableImpl +import org.openbase.jul.pattern.Observer +import org.openbase.jul.pattern.provider.DataProvider +import org.openbase.jul.schedule.GlobalCachedExecutorService +import org.openbase.jul.schedule.Timeout +import org.openbase.type.domotic.service.ServiceTemplateType.ServiceTemplate.ServiceType +import org.openbase.type.domotic.state.* +import org.openbase.type.domotic.state.MotionStateType.MotionState +import org.openbase.type.domotic.state.MotionStateType.MotionStateOrBuilder +import org.openbase.type.domotic.state.PresenceStateType.PresenceState +import org.openbase.type.domotic.state.PresenceStateType.PresenceStateOrBuilder +import org.openbase.type.domotic.state.WindowStateType.WindowState +import org.openbase.type.domotic.unit.UnitConfigType.UnitConfig +import org.openbase.type.domotic.unit.UnitTemplateType.UnitTemplate.UnitType +import org.openbase.type.domotic.unit.connection.ConnectionConfigType.ConnectionConfig.ConnectionType +import org.openbase.type.domotic.unit.location.LocationConfigType.LocationConfig.LocationType +import org.openbase.type.domotic.unit.location.LocationDataType.LocationData +import org.openbase.type.domotic.unit.location.TileConfigType.TileConfig.TileType +import org.slf4j.Logger +import org.slf4j.LoggerFactory +import java.time.Duration +import java.time.Instant +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit +import kotlin.math.max + +/** + * @author [Divine Threepwood](mailto:divine@openbase.org) + */ +class PresenceDetector : Manageable, DataProvider { + private val logger: Logger = LoggerFactory.getLogger(javaClass) + + private val presenceStateBuilder: PresenceState.Builder = PresenceState.newBuilder() + private var presenceTimeout: Timeout? = null + private val locationDataObserver: Observer, LocationData> + private var location: Location? = null + private val presenceStateObservable = ObservableImpl, PresenceState>(this) + private val buttonUnitPool: CustomUnitPool> + private val connectionUnitPool: CustomUnitPool> + + private var active = false + private var isShutdownInitiated: Boolean = false + private set + + init { + object : Timeout(PRESENCE_TIMEOUT) { + override fun expired() { + if (location == null) { + return + } + + try { + // if motion is still detected just restart the timeout. + if (location?.data?.motionState?.value == MotionState.State.MOTION && + durationSinceLastMotion < MOTION_TIMEOUT + ) { + GlobalCachedExecutorService.submit { + try { + presenceTimeout?.restart() + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory("Could not setup presence timeout!", ex, logger) + } + } + return + } + + updatePresenceState( + PresenceState.newBuilder() + .setValue(PresenceState.State.ABSENT) + ) + } catch (ex: ShutdownInProgressException) { + // skip update on shutdown + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory( + CouldNotPerformException("Could not notify absent by timer!", ex), + logger + ) + } + } + }.also { this.presenceTimeout = it } + + locationDataObserver = Observer { _: DataProvider, data: LocationData -> + updateMotionState(data.motionState) + } + + this.buttonUnitPool = CustomUnitPool() + this.connectionUnitPool = CustomUnitPool() + + buttonUnitPool.addServiceStateObserver { _: ServiceStateProvider, data: Message -> + try { + this@PresenceDetector.updateButtonState(data as ButtonStateType.ButtonState) + } catch (ex: ClassCastException) { + ExceptionPrinter.printHistory("ButtonPool entail incompatible units!", ex, logger) + } + } + + connectionUnitPool.addServiceStateObserver { source: ServiceStateProvider, data: Message -> + when (source.serviceType) { + ServiceType.WINDOW_STATE_SERVICE -> updateWindowState(data as WindowState) + ServiceType.DOOR_STATE_SERVICE -> updateDoorState(data as DoorStateType.DoorState) + ServiceType.PASSAGE_STATE_SERVICE -> {} + else -> logger.warn( + "Invalid connection service update received: " + + source.serviceType.name + " from " + source + " pool:" + connectionUnitPool.isActive() + ) + } + } + } + + @Throws(InitializationException::class, InterruptedException::class) + override fun init(location: Location) { + try { + this.location = location + buttonUnitPool.init( + Filter { unitConfig: UnitConfig -> unitConfig.unitType == UnitType.BUTTON }, + Filter { unitConfig: UnitConfig -> + try { + unitConfig.placementConfig.locationId == location.id + } catch (ex: NotAvailableException) { + ExceptionPrinter.printHistory( + "Could not resolve location id within button filter operation.", + ex, + logger + ) + true + } + } + ) + + if ((location.config.locationConfig.locationType == LocationType.TILE)) { + connectionUnitPool.init( + Filter { unitConfig -> unitConfig.unitType == UnitType.CONNECTION }, + Filter { unitConfig -> + try { + unitConfig.connectionConfig.tileIdList.contains(location.id) + } catch (ex: NotAvailableException) { + ExceptionPrinter.printHistory( + "Could not resolve location id within connection filter operation.", + ex, + logger + ) + false + } + }, + Filter { unitConfig -> + try { + location.config.locationConfig.tileConfig.tileType != TileType.OUTDOOR + || unitConfig.connectionConfig.connectionType != ConnectionType.WINDOW + } catch (ex: NotAvailableException) { + ExceptionPrinter.printHistory( + "Could not resolve location id within connection filter operation.", + ex, + logger + ) + false + } + }) + } + } catch (ex: CouldNotPerformException) { + throw InitializationException(this, ex) + } + } + + @Throws(InitializationException::class, InterruptedException::class) + fun init(location: Location, motionTimeout: Long) { + init(location) + presenceTimeout?.defaultWaitTime = motionTimeout + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun activate() { + active = true + location?.addDataObserver(locationDataObserver) + + buttonUnitPool.activate() + + if (location?.config?.locationConfig?.locationType == LocationType.TILE) { + connectionUnitPool.activate() + } + + // start initial timeout + presenceTimeout?.start() + location?.data?.motionState?.let { updateMotionState(it) } + } + + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun deactivate() { + active = false + presenceTimeout?.cancel() + location?.removeDataObserver(locationDataObserver) + buttonUnitPool.deactivate() + if ((location?.config?.locationConfig?.locationType == LocationType.TILE)) { + connectionUnitPool.deactivate() + } + } + + override fun isActive(): Boolean { + return active + } + + @Throws(InvalidStateException::class) + override fun validateData() { + if (isShutdownInitiated) { + throw InvalidStateException(ShutdownInProgressException(this)) + } + + if (isDataAvailable) { + throw InvalidStateException(NotAvailableException("Data")) + } + } + + override fun shutdown() { + try { + isShutdownInitiated = true + deactivate() + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory(ex, logger) + } catch (ex: InterruptedException) { + ExceptionPrinter.printHistory(ex, logger) + } + buttonUnitPool.shutdown() + connectionUnitPool.shutdown() + } + + @Synchronized + @Throws(CouldNotPerformException::class) + private fun updatePresenceState(presenceState: PresenceStateOrBuilder) { + // update timestamp and reset timer + if (presenceState.value == PresenceState.State.PRESENT + && presenceStateBuilder.timestamp.time != presenceState.timestamp.time + ) { + presenceTimeout?.restart() + presenceStateBuilder.timestampBuilder.setTime( + max(presenceStateBuilder.timestamp.time, presenceState.timestamp.time) + ) + } + + // filter non-state changes + if (presenceStateBuilder.value == presenceState.value) { + return + } + + // update value + TimestampProcessor.updateTimestampWithCurrentTime(presenceStateBuilder, logger) + presenceStateBuilder.setValue(presenceState.value) + + // notify + try { + presenceStateObservable.notifyObservers(presenceStateBuilder.build()) + } catch (ex: CouldNotPerformException) { + ExceptionPrinter.printHistory( + CouldNotPerformException("Could not update MotionState!", ex), + logger, + LogLevel.ERROR + ) + } + } + + @Throws(CouldNotPerformException::class) + private fun updateMotionState(motionState: MotionStateOrBuilder) { + if (motionState.value == MotionState.State.NO_MOTION) { + return + } + + if (motionState.value == MotionState.State.MOTION && + durationSinceLastMotion(motionState) < MOTION_TIMEOUT + ) { + updatePresenceState( + TimestampProcessor.updateTimestampWithCurrentTime( + PresenceState + .newBuilder() + .setValue(PresenceState.State.PRESENT) + .setResponsibleAction(motionState.responsibleAction) + .build() + ) + ) + } + } + + @Throws(CouldNotPerformException::class) + private fun updateButtonState(buttonState: ButtonStateType.ButtonStateOrBuilder) { + when (buttonState.value) { + ButtonStateType.ButtonState.State.PRESSED, + ButtonStateType.ButtonState.State.RELEASED, + ButtonStateType.ButtonState.State.DOUBLE_PRESSED, + -> { + // this causes a lot of trouble, thus its currently disabled until we have a proper solution. + // since triggering an "all off" scene via a button could cause lights to be switched on again. + } + + else -> {} + } + } + + @Throws(CouldNotPerformException::class) + private fun updateDoorState(doorState: DoorStateType.DoorState) { + when (doorState.value) { + DoorStateType.DoorState.State.OPEN -> { + updatePresenceState( + TimestampProcessor.updateTimestampWithCurrentTime( + PresenceState.newBuilder() + .setValue(PresenceState.State.PRESENT) + .setResponsibleAction(doorState.responsibleAction) + .build() + ) + ) + } + + else -> {} + } + } + + @Throws(CouldNotPerformException::class) + private fun updateWindowState(windowState: WindowState) { + when (windowState.value) { + WindowState.State.OPEN, WindowState.State.TILTED, WindowState.State.CLOSED -> { + updatePresenceState( + TimestampProcessor.updateTimestampWithCurrentTime( + PresenceState.newBuilder() + .setValue(PresenceState.State.PRESENT) + .setResponsibleAction(windowState.responsibleAction) + .build() + ) + ) + } + + else -> {} + } + } + + override fun isDataAvailable(): Boolean { + return presenceStateObservable.isValueAvailable + } + + override fun getDataClass(): Class { + return PresenceState::class.java + } + + @Throws(NotAvailableException::class) + override fun getData(): PresenceState { + return presenceStateObservable.value + } + + override fun getDataFuture(): Future { + return presenceStateObservable.valueFuture + } + + override fun addDataObserver(observer: Observer, PresenceState>) { + presenceStateObservable.addObserver(observer) + } + + override fun removeDataObserver(observer: Observer, PresenceState>) { + presenceStateObservable.removeObserver(observer) + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun waitForData() { + presenceStateObservable.waitForValue() + } + + @Throws(CouldNotPerformException::class, InterruptedException::class) + override fun waitForData(timeout: Long, timeUnit: TimeUnit) { + presenceStateObservable.waitForValue(timeout, timeUnit) + } + + val durationSinceLastMotion: Duration + get() = location?.data?.motionState?.let { motion -> + tryOrNull { + ServiceStateProcessor.getLatestValueOccurrence(MotionState.State.MOTION, motion)?.instant + ?.let { lastEvent -> Duration.between(lastEvent, Instant.now()) } + } + } ?: MOTION_TIMEOUT + + fun durationSinceLastMotion(motionState: MotionStateOrBuilder? = location?.data?.motionState): Duration = + motionState?.let { motion -> + tryOrNull { + ServiceStateProcessor.getLatestValueOccurrence(MotionState.State.MOTION, motion)?.instant + ?.let { lastEvent -> Duration.between(lastEvent, Instant.now()) } + } + } ?: MOTION_TIMEOUT + + companion object { + val PRESENCE_TEST_TIMEOUT: Duration = Duration.ofMillis(50) + @JvmField + val PRESENCE_TIMEOUT: Duration = + Duration.ofMinutes(1).takeIf { JPService.testMode().not() } + ?: PRESENCE_TEST_TIMEOUT + @JvmField + val MOTION_TIMEOUT: Duration = Duration.ofHours(1) + } +} diff --git a/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/location/LocationRemoteTest.java b/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/location/LocationRemoteTest.java index b6b4fff0df..2ccda3cfde 100644 --- a/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/location/LocationRemoteTest.java +++ b/module/dal/test/src/test/java/org/openbase/bco/dal/test/layer/unit/location/LocationRemoteTest.java @@ -375,7 +375,7 @@ public void testPresenceState() throws Exception { motionDetectorController.applyServiceState(Motion.NO_MOTION, ServiceType.MOTION_STATE_SERVICE); - Thread.sleep(PresenceDetector.PRESENCE_TIMEOUT); + Thread.sleep(PresenceDetector.PRESENCE_TIMEOUT.toMillis()); while (rootLocationRemote.getPresenceState().getValue() != PresenceState.State.ABSENT) { System.out.println("Waiting for locationRemote presenceState update!"); Thread.sleep(10); diff --git a/versions.properties b/versions.properties index 34b241fe1c..bfe39a4011 100644 --- a/versions.properties +++ b/versions.properties @@ -12,7 +12,7 @@ version.io.quarkus..quarkus-junit4-mock=3.2.3.Final ## unused version.junit.jupiter=5.10.0 version.kotest=5.6.2 -version.kotlinx.coroutines=1.7.3 +version.kotlinx.coroutines=1.8.0 version.mockk=1.13.5 version.org.glassfish.jersey.security..oauth2-client=3.1.3 version.org.glassfish.jersey.media..jersey-media-sse=3.1.3 @@ -58,7 +58,7 @@ version.org.springframework..spring-webmvc=6.0.11 version.org.openhab.core.bundles..org.openhab.core.io.rest.core=4.0.4 plugin.org.springframework.boot=3.1.2 plugin.io.spring.dependency-management=1.1.2 -version.kotlin=1.9.0 +version.kotlin=1.9.20 version.org.openbase..jul.communication.controller=3.6-SNAPSHOT ## # available=3.0.0 ## # available=3.0.1