Skip to content

Commit

Permalink
Merge pull request #45 from OSGP/feature/FDP-2874-alarm-info
Browse files Browse the repository at this point in the history
Feature/fdp 2874 alarm info
  • Loading branch information
loesimmens authored Feb 10, 2025
2 parents 0e5216a + e9918d4 commit 6423233
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,20 @@ package org.gxf.crestdevicesimulator.simulator.data.entity

import org.gxf.crestdevicesimulator.simulator.message.DeviceMessageDownlink

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()
/**
* Simulator state
*
* @property fotaMessageCounter the counter used for firmware updates, defaults to 0
* @property urcs the URCs that will be sent in the next message, defaults to INIT at startup
*/
class SimulatorState(var fotaMessageCounter: Int = 0, private val urcs: MutableList<String> = mutableListOf("INIT")) {
private val downlinks: MutableList<String> = mutableListOf()
private val alarmThresholds: MutableMap<Int, AlarmThresholdValues> = 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)
(0..7).associateWith { AlarmThresholdValues(it, 0, 0, 0, 0, 0) }.toMutableMap()

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

fun resetUrc() {
urcs.clear()
Expand All @@ -39,10 +33,7 @@ class SimulatorState(var fotaMessageCounter: Int = 0) {
alarmThresholds[alarmThresholdValues.channel] = alarmThresholdValues
}

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

fun resetAlarmThresholds() {
alarmThresholds.clear()
alarmThresholds += defaultAlarmThresholds()
}
fun getAlarmThresholds(index: Int) = alarmThresholds[index]
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class AlarmCommandHandler : CommandHandler {

try {
handleAlarmCommand(command, simulatorState)
} catch (ex: InvalidCommandException) {
} catch (_: InvalidCommandException) {
handleFailure(command, simulatorState)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0
package org.gxf.crestdevicesimulator.simulator.response.handlers

import io.github.oshai.kotlinlogging.KotlinLogging
import org.gxf.crestdevicesimulator.simulator.data.entity.AlarmThresholdValues
import org.gxf.crestdevicesimulator.simulator.data.entity.SimulatorState
import org.gxf.crestdevicesimulator.simulator.response.exception.InvalidCommandException

/**
* Command Handler for INFO:AL# and INFO:ALARM downlinks.
*
* Gets the alarm thresholds for the specified alarm or for all alarms
* * On success: a downlink containing the alarm thresholds will be returned in the next message sent
* * On failure: "INFO:DLER" URC will be returned in the next message sent
*/
class InfoAlarmCommandHandler : CommandHandler {
private val logger = KotlinLogging.logger {}
private val commandRegex: Regex = "INFO:AL([2-7]|ARM)".toRegex()

override fun canHandleCommand(command: String) = commandRegex.matches(command)

override fun handleCommand(command: String, simulatorState: SimulatorState) {
require(canHandleCommand(command)) { "Info alarm command handler can not handle command: $command" }
try {
handleInfoAlarmCommand(command, simulatorState)
} catch (_: InvalidCommandException) {
handleFailure(command, simulatorState)
}
}

private fun handleInfoAlarmCommand(command: String, simulatorState: SimulatorState) {
logger.info { "Handling info alarm command: $command" }
try {
simulatorState.addDownlink(command.inQuotes())
if (isForSpecificAlarm(command)) {
val channel: Int = getChannelFromCommand(command)
val thresholds =
checkNotNull(simulatorState.getAlarmThresholds(channel)) {
"Alarm thresholds for channel $channel not present."
}
simulatorState.addDownlink(thresholds.toJsonString().inBraces())
} else {
simulatorState.addDownlink(simulatorState.getAlarmThresholds().values.toJsonString())
}
} catch (ex: Exception) {
throw InvalidCommandException("Invalid info alarm command", ex)
}
}

private fun handleFailure(command: String, simulatorState: SimulatorState) {
logger.warn { "Handling failure for info alarm command: $command" }
simulatorState.addUrc("INFO:DLER")
simulatorState.addDownlink(command.inQuotes())
}

private fun isForSpecificAlarm(command: String) = !isForAllAlarms(command)

private fun isForAllAlarms(command: String) = command.contains("ALARM")

private fun getChannelFromCommand(command: String) = command.substringAfter("AL").substringBefore(":").toInt()

private fun MutableCollection<AlarmThresholdValues>.toJsonString() =
this.joinToString(",") { it.toJsonString() }.inBraces()

private fun String.inQuotes() = "\"$this\""

private fun String.inBraces() = "{$this}"

private fun AlarmThresholdValues.toJsonString() =
"\"AL${this.channel}\":[${this.veryLow},${this.low},${this.high},${this.veryHigh},${this.hysteresis}]"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ class AlarmCommandHandlerTest {

@BeforeEach
fun setUp() {
simulatorState = SimulatorState()
simulatorState.resetUrc()
simulatorState.resetAlarmThresholds()
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0
package org.gxf.crestdevicesimulator.simulator.response.handlers

import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException
import org.gxf.crestdevicesimulator.simulator.data.entity.AlarmThresholdValues
import org.gxf.crestdevicesimulator.simulator.data.entity.SimulatorState
import org.gxf.crestdevicesimulator.simulator.message.DeviceMessageDownlink
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.ValueSource

class InfoAlarmCommandHandlerTest {
private val commandHandler = InfoAlarmCommandHandler()

private lateinit var simulatorState: SimulatorState

@BeforeEach
fun setUp() {
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
@ValueSource(strings = ["INFO:ALARM", "INFO:AL7"])
fun `canHandleCommand should return true when called with valid command`(command: String) {
val actualResult = commandHandler.canHandleCommand(command)
assertThat(actualResult).isTrue
}

@ParameterizedTest
@ValueSource(strings = ["INFO", "INFO:", "INFO:AL", "INFO:AL10", "CMD:REBOOT", "CMD:RSP"])
fun `canHandleCommand should return false when called with invalid command`(command: String) {
val actualResult = commandHandler.canHandleCommand(command)
assertThat(actualResult).isFalse
}

@Test
fun `handleCommand should handle info alarm command`() {
val command = "INFO:ALARM"
val alarmThresholdValues = AlarmThresholdValues(6, 0, 500, 1000, 1500, 10)
simulatorState.addAlarmThresholds(alarmThresholdValues)

commandHandler.handleCommand(command, simulatorState)

val expectedDownlink =
"""
"INFO:ALARM",{"AL0":[0,0,0,0,0],"AL1":[0,0,0,0,0],"AL2":[0,0,0,0,0],"AL3":[0,0,0,0,0],
"AL4":[0,0,0,0,0],"AL5":[0,0,0,0,0],"AL6":[0,500,1000,1500,10],"AL7":[0,0,0,0,0]}
"""
.trimIndent()
.replace("\n", "")
.replace("\r", "")
assertThat(simulatorState.getUrcListForDeviceMessage()).contains(DeviceMessageDownlink(expectedDownlink))
}

@Test
fun `handleCommand should handle info alarm command for a specific alarm`() {
val command = "INFO:AL7"
val alarmThresholdValues = AlarmThresholdValues(7, 0, 500, 1000, 1500, 10)
simulatorState.addAlarmThresholds(alarmThresholdValues)

commandHandler.handleCommand(command, simulatorState)

val expectedDownlink = """"INFO:AL7",{"AL7":[0,500,1000,1500,10]}"""
assertThat(simulatorState.getUrcListForDeviceMessage()).contains(DeviceMessageDownlink(expectedDownlink))
}

@ParameterizedTest
@ValueSource(strings = ["INFO", "INFO:", "INFO:AL", "INFO:AL10", "CMD:REBOOT", "CMD:RSP"])
fun `handleCommand should throw an exception and not change simulator state when called with invalid command`(
command: String
) {
assertThatIllegalArgumentException().isThrownBy { commandHandler.handleCommand(command, simulatorState) }

assertThat(simulatorState.getUrcListForDeviceMessage()).doesNotContain(DeviceMessageDownlink(command))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ class PskCommandHandlerTest {

@BeforeEach
fun setUp() {
simulatorState = SimulatorState()
simulatorState.resetUrc()
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ class PskSetCommandHandlerTest {

@BeforeEach
fun setUp() {
simulatorState = SimulatorState()
simulatorState.resetUrc()
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class RebootCommandHandlerTest {

@BeforeEach
fun setUp() {
simulatorState = SimulatorState()
simulatorState.resetUrc()
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class Rsp2CommandHandlerTest {

@BeforeEach
fun setUp() {
simulatorState = SimulatorState()
simulatorState.resetUrc()
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ class RspCommandHandlerTest {

@BeforeEach
fun setUp() {
simulatorState = SimulatorState()
simulatorState.resetUrc()
simulatorState = SimulatorState(urcs = mutableListOf())
}

@ParameterizedTest
Expand Down

0 comments on commit 6423233

Please sign in to comment.