Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FDP-2929 ~ Adds alarm command handling and ... #44

Merged
merged 7 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 28 additions & 31 deletions application/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,28 @@
//
// SPDX-License-Identifier: Apache-2.0

plugins {
id("org.springframework.boot")
}
plugins { id("org.springframework.boot") }

dependencies {

implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")

implementation(kotlin("reflect"))
implementation(libs.bundles.californium)
implementation(libs.logging)

implementation(libs.commonsCodec)
implementation(libs.jacksonDataformatCbor)
implementation(libs.jakartaXmlBindApi)
implementation(libs.kotlinReflect)
implementation(libs.logging)
implementation(libs.springBootStarterActuator)
implementation(libs.springBootStarterDataJpa)
implementation(libs.springBootStarterWeb)

implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
implementation("jakarta.xml.bind:jakarta.xml.bind-api")

runtimeOnly("io.micrometer:micrometer-registry-prometheus")
runtimeOnly("org.flywaydb:flyway-database-postgresql")
runtimeOnly("org.postgresql:postgresql")
runtimeOnly(libs.flyway)
runtimeOnly(libs.micrometerPrometheusModule)
runtimeOnly(libs.postgresql)

testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation(testLibs.mockitoKotlin)
testImplementation(libs.mockk)
testImplementation(libs.springBootStarterTest)
testImplementation(libs.springmockk)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly(libs.junitPlatformLauncher)

// Generate test and integration test reports
jacocoAggregation(project(":application"))
Expand All @@ -49,17 +44,19 @@ tasks.withType<org.springframework.boot.gradle.tasks.bundling.BootBuildImage> {

testing {
suites {
val integrationTest by registering(JvmTestSuite::class) {
useJUnitJupiter()
dependencies {
implementation(project())
implementation("org.springframework.boot:spring-boot-starter-test")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-cbor")
implementation.bundle(libs.bundles.californium)
implementation("org.awaitility:awaitility")
runtimeOnly("com.h2database:h2")
val integrationTest by
registering(JvmTestSuite::class) {
useJUnitJupiter()
dependencies {
implementation(project())
implementation(libs.awaitility)
implementation(libs.jacksonDataformatCbor)
implementation(libs.mockk)
implementation(libs.springBootStarterDataJpa)
implementation(libs.springBootStarterTest)
implementation.bundle(libs.bundles.californium)
runtimeOnly(libs.h2)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class AdvancedSingleIdentityPskStore(private val identity: String) : AdvancedPsk
hmacAlgorithm: String?,
otherSecret: SecretKey?,
seed: ByteArray?,
useExtendedMasterSecret: Boolean
useExtendedMasterSecret: Boolean,
): PskSecretResult {
if (key.isEmpty()) {
return PskSecretResult(cid, identity, SecretUtil.create(defaultKey.toByteArray(), ALGORITHM))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ import org.springframework.context.annotation.Bean
@org.springframework.context.annotation.Configuration
class CoapClientConfiguration(
private val simulatorProperties: SimulatorProperties,
private val pskRepository: PskRepository
private val pskRepository: PskRepository,
) {

@Bean
fun pskStore(): AdvancedSingleIdentityPskStore {
val store = AdvancedSingleIdentityPskStore(simulatorProperties.pskIdentity)
val savedKey =
pskRepository.findFirstByIdentityAndStatusOrderByRevisionDesc(
simulatorProperties.pskIdentity, PreSharedKeyStatus.ACTIVE)
simulatorProperties.pskIdentity,
PreSharedKeyStatus.ACTIVE,
)

if (savedKey == null) {
val initialPreSharedKey =
Expand All @@ -28,7 +30,8 @@ class CoapClientConfiguration(
0,
simulatorProperties.pskKey,
simulatorProperties.pskSecret,
PreSharedKeyStatus.ACTIVE)
PreSharedKeyStatus.ACTIVE,
)
pskRepository.save(initialPreSharedKey)
store.key = simulatorProperties.pskKey
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ import org.springframework.boot.CommandLineRunner
import org.springframework.stereotype.Component

@Component
class Simulator(
private val simulatorProperties: SimulatorProperties,
private val messageHandler: MessageHandler,
) : CommandLineRunner {
class Simulator(private val simulatorProperties: SimulatorProperties, private val messageHandler: MessageHandler) :
CommandLineRunner {

private val logger = KotlinLogging.logger {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import org.springframework.stereotype.Service
class CoapClientService(
private val simulatorProperties: SimulatorProperties,
private val advancedSingleIdentityPskStore: AdvancedSingleIdentityPskStore,
private val configuration: Configuration
private val configuration: Configuration,
) {

fun shutdownCoapClient(coapClient: CoapClient) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0
package org.gxf.crestdevicesimulator.simulator.data.entity

data class AlarmThresholdValues(
val channel: Int,
val veryLow: Int,
val low: Int,
val high: Int,
val veryHigh: Int,
val hysteresis: Int,
)
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ class PreSharedKey(
@Id val revision: Int,
var preSharedKey: String,
val secret: String,
@Enumerated(EnumType.STRING) var status: PreSharedKeyStatus
@Enumerated(EnumType.STRING) var status: PreSharedKeyStatus,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ enum class PreSharedKeyStatus {
ACTIVE,
INACTIVE,
PENDING,
INVALID
INVALID,
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ class SimulatorState(var fotaMessageCounter: Int = 0) {
private val urcs = mutableListOf("INIT") // INIT = boot, will be reset for second message
private val downlinks = mutableListOf<String>()

private val alarmThresholds = defaultAlarmThresholds()

private fun defaultAlarmThresholds() =
mutableMapOf(
2 to defaultAlarmThresholdValues(2),
3 to defaultAlarmThresholdValues(3),
4 to defaultAlarmThresholdValues(4),
5 to defaultAlarmThresholdValues(5),
6 to defaultAlarmThresholdValues(6),
7 to defaultAlarmThresholdValues(7),
)

private fun defaultAlarmThresholdValues(channel: Int) = AlarmThresholdValues(channel, 0, 0, 0, 0, 0)

fun getUrcListForDeviceMessage(): List<Any> = urcs + listOf(DeviceMessageDownlink(downlinks.joinToString(";")))

fun resetUrc() {
Expand All @@ -20,4 +34,15 @@ class SimulatorState(var fotaMessageCounter: Int = 0) {
fun addUrc(urc: String) = apply { urcs += urc }

fun addDownlink(downlink: String) = apply { downlinks += downlink }

fun addAlarmThresholds(alarmThresholdValues: AlarmThresholdValues) = apply {
alarmThresholds[alarmThresholdValues.channel] = alarmThresholdValues
}

fun getAlarmThresholds(index: Int) = alarmThresholds[index]

fun resetAlarmThresholds() {
alarmThresholds.clear()
alarmThresholds += defaultAlarmThresholds()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0
package org.gxf.crestdevicesimulator.simulator.event

import org.gxf.crestdevicesimulator.simulator.message.DeviceMessage

data class MessageSentEvent(val message: DeviceMessage)
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ data class DeviceMessage(
2048,
2048,
2048,
2048),
2048,
),
var p2: List<Int> =
listOf(
1800,
Expand Down Expand Up @@ -170,7 +171,8 @@ data class DeviceMessage(
2048,
2048,
2048,
2048),
2048,
),
var pwr: Int = 1,
var rly: Int = 0,
var rsrp: Int = -99,
Expand All @@ -183,7 +185,7 @@ data class DeviceMessage(
var tsl: Int = 1693318384,
var upt: Int = 100,
var urc: List<Any> = listOf("INIT", DeviceMessageDownlink()),
@JsonProperty("cID") var cid: Int = 49093243
@JsonProperty("cID") var cid: Int = 49093243,
)

@JsonNaming(PropertyNamingStrategies.UpperSnakeCaseStrategy::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,21 @@ import org.gxf.crestdevicesimulator.configuration.SimulatorProperties
import org.gxf.crestdevicesimulator.simulator.CborFactory
import org.gxf.crestdevicesimulator.simulator.coap.CoapClientService
import org.gxf.crestdevicesimulator.simulator.data.entity.SimulatorState
import org.gxf.crestdevicesimulator.simulator.response.CommandService
import org.gxf.crestdevicesimulator.simulator.response.PskExtractor
import org.gxf.crestdevicesimulator.simulator.response.command.PskService
import org.gxf.crestdevicesimulator.simulator.event.MessageSentEvent
import org.gxf.crestdevicesimulator.simulator.response.handlers.CommandHandler
import org.springframework.context.ApplicationEventPublisher
import org.springframework.stereotype.Service

@Service
class MessageHandler(
private val coapClientService: CoapClientService,
private val simulatorProperties: SimulatorProperties,
private val pskService: PskService,
private val mapper: ObjectMapper,
private val commandService: CommandService,
private val handlers: MutableList<out CommandHandler>,
private val jacksonObjectMapper: ObjectMapper
private val jacksonObjectMapper: ObjectMapper,
private val applicationEventPublisher: ApplicationEventPublisher,
) {
private val logger = KotlinLogging.logger {}

companion object {
private const val URC_FIELD = "URC"
private const val URC_PSK_SUCCESS = "PSK:SET"
private const val URC_PSK_ERROR = "PSK:EQER"
private const val REBOOT_SUCCESS = "INIT"
private const val DL_FIELD = "DL"
}

fun sendMessage(simulatorState: SimulatorState): Boolean {
val messageToBeSent = createMessageFromCurrentState(simulatorState)
val request = createRequest(messageToBeSent)
Expand All @@ -51,11 +40,14 @@ class MessageHandler(
try {
coapClient = coapClientService.createCoapClient()
val response = coapClient.advanced(request)
logger.info { "Received Response: ${response.payload.decodeToString()} with status ${response.code}" }
handleResponse(response, simulatorState)
immediateResponseRequested = String(response.payload).startsWith("!")
if (response.isSuccess) {
applicationEventPublisher.publishEvent(MessageSentEvent(messageToBeSent))
immediateResponseRequested = handleResponse(response, simulatorState)
} else {
logger.error { "Received error response with ${response.code}" }
}
} catch (e: Exception) {
e.printStackTrace()
logger.error(e) { "Exception occurred while trying to send a message." }
} finally {
if (coapClient != null) coapClientService.shutdownCoapClient(coapClient)
}
Expand All @@ -81,62 +73,24 @@ class MessageHandler(
.setPayload(payload)
}

private fun handleResponse(response: CoapResponse, simulatorState: SimulatorState) {
if (response.isSuccess) {
val payload = String(response.payload)
when {
// The PSK:SET command should be handled with the current PSK
PskExtractor.hasPskSetCommand(payload) -> {
handlePskSetCommand(payload, simulatorState)
}
// On the next response, activate the new PSK
pskService.isPendingKeyPresent() -> {
pskService.changeActiveKey()
}
commandService.hasRebootCommand(payload) -> {
sendRebootSuccesMessage(payload, simulatorState)
}
}
handlers.forEach { handler -> handler.handleResponse(response, simulatorState) }
} else {
logger.error { "Received error response with ${response.code}" }
if (pskService.isPendingKeyPresent()) {
logger.error { "Error received. Set pending key to invalid" }
pskService.setPendingKeyAsInvalid()
}
}
}

private fun handlePskSetCommand(payload: String, simulatorState: SimulatorState) {
try {
logger.info { "Device ${simulatorProperties.pskIdentity} needs key change" }
pskService.preparePendingKey(payload)
sendPskSetSuccessMessage(payload, simulatorState)
} catch (e: Exception) {
logger.error(e) { "PSK change error, send failure message and set pending key status to invalid" }
sendPskSetFailureMessage(payload, simulatorState)
pskService.setPendingKeyAsInvalid()
}
private fun handleResponse(response: CoapResponse, simulatorState: SimulatorState): Boolean {
val payload = String(response.payload)
logger.info { "Received Response: $payload with status ${response.code}" }
val immediateResponseRequested = payload.isImmediateResponseRequested()
handleDownlinks(payload.stripImmediateResponseMarker(), simulatorState)
return immediateResponseRequested
}

private fun sendPskSetSuccessMessage(pskCommand: String, simulatorState: SimulatorState) {
logger.info { "Sending success message for command $pskCommand" }
simulatorState.addUrc(URC_PSK_SUCCESS)
simulatorState.addDownlink("PSK:####:SET")
sendMessage(simulatorState)
}
private fun String.isImmediateResponseRequested() = this.startsWith("!")

private fun sendPskSetFailureMessage(pskCommand: String, simulatorState: SimulatorState) {
logger.warn { "Sending failure message for command $pskCommand" }
simulatorState.addUrc(URC_PSK_ERROR)
simulatorState.addDownlink("PSK:####:SET")
sendMessage(simulatorState)
}
private fun String.stripImmediateResponseMarker() = this.replace("^!".toRegex(), "")

private fun sendRebootSuccesMessage(command: String, simulatorState: SimulatorState) {
logger.info { "Sending success message for command $command" }
simulatorState.addUrc(REBOOT_SUCCESS)
simulatorState.addDownlink("CMD:REBOOT")
sendMessage(simulatorState)
private fun handleDownlinks(downlinks: String, simulatorState: SimulatorState) {
val commands = downlinks.split(";")
commands.forEach { command ->
handlers.forEach { handler ->
if (handler.canHandleCommand(command)) handler.handleCommand(command, simulatorState)
}
}
}
}

This file was deleted.

Loading
Loading