Skip to content

Commit

Permalink
Merge pull request #100 from OSGP/feature/FDP-2874-alarm-msgs
Browse files Browse the repository at this point in the history
FDP-2874: Fixed handling of Alarm device messages
  • Loading branch information
sanderv authored Feb 20, 2025
2 parents b8d0386 + 257fc44 commit 3edfcfd
Show file tree
Hide file tree
Showing 9 changed files with 92 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,8 @@ abstract class CommandResultHandler(

companion object {
private const val URC_FIELD = "URC"
private const val DL_FIELD = "DL"

/** Filters out Text elements from the JSON node. "DL" is in an object, so it won't be in the resulting list */
fun JsonNode.urcs(): List<String> = this[URC_FIELD].filter { it.isTextual }.map { it.asText() }

fun JsonNode.downlinks(): List<String> =
this[URC_FIELD].first { it.isObject }[DL_FIELD].asText().replace("!", "").split(";")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ class InfoAlarmsResultHandler(

override val supportedCommandType = Command.CommandType.INFO_ALARMS

override fun hasSucceeded(command: Command, message: JsonNode) =
try {
alarmsInfoService.getAlarmsInfo(message)
true
} catch (_: Exception) {
false
}
override fun hasSucceeded(command: Command, message: JsonNode) = alarmsInfoService.containsResult(message)

override fun hasFailed(command: Command, message: JsonNode): Boolean = message.urcs().any { it in errorUrcs }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@ package org.gxf.crestdeviceservice.command.service

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.treeToValue
import io.github.oshai.kotlinlogging.KotlinLogging
import org.gxf.crestdeviceservice.command.entity.Command
import org.gxf.crestdeviceservice.command.resulthandler.CommandResultHandler.Companion.downlinks
import org.gxf.crestdeviceservice.model.AlarmsInfo
import org.gxf.crestdeviceservice.model.DeviceMessage
import org.springframework.stereotype.Service

@Service
class AlarmsInfoService {
private val logger = KotlinLogging.logger {}
private val mapper = jacksonObjectMapper()
private val commandRegex = "!?${Command.CommandType.INFO_ALARMS.downlink}".toRegex()

fun getAlarmsInfo(message: JsonNode): AlarmsInfo {
val downlink = message.downlinks().first { it.contains(Command.CommandType.INFO_ALARMS.downlink) }
val json = downlink.substringAfter(", ")
fun containsResult(message: JsonNode) = DeviceMessage(message).getDownlinkCommand()?.matches(commandRegex) == true

return mapper.readValue<AlarmsInfo>(json)
/** The device answer is logged, no further action is taken */
fun getAlarmsInfo(message: JsonNode): AlarmsInfo {
require(containsResult(message))
val deviceMessage = DeviceMessage(message)
val alarmThresholds = deviceMessage.getUrcContainingField("AL0")
logger.debug { "Received alarms info: $alarmThresholds" }
return mapper.treeToValue<AlarmsInfo>(alarmThresholds)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import com.fasterxml.jackson.databind.JsonNode
class DeviceMessage(val message: JsonNode) {
fun getFotaMessageCounter() = message[FMC_FIELD].intValue()

fun getUrcContainingField(fieldName: String): JsonNode = message.path(URC_FIELD).findParent(fieldName)

fun getDownlinkCommand() = message.path(URC_FIELD).findValue(DL_FIELD)?.asText()

companion object {
const val FMC_FIELD = "FMC"
const val URC_FIELD = "URC"
const val DL_FIELD = "DL"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@
package org.gxf.crestdeviceservice

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.fasterxml.jackson.databind.node.ObjectNode
import com.fasterxml.jackson.databind.node.TextNode
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import org.springframework.util.ResourceUtils

object MessageFactory {
private const val URC_FIELD = "URC"
private const val DL_FIELD = "DL"

private val mapper = ObjectMapper()
private val mapper = jacksonObjectMapper()

fun messageTemplate(): ObjectNode {
val messageFile = ResourceUtils.getFile("classpath:message-template.json")
return mapper.readTree(messageFile) as ObjectNode
}

fun messageWithUrc(urcs: List<String>, downlink: String = ""): JsonNode {
val urcNodes = urcs.map { urc -> TextNode(urc) }
val downlinkNode = ObjectNode(JsonNodeFactory.instance, mapOf(DL_FIELD to TextNode(downlink)))

val urcFieldValue: ArrayNode = ObjectMapper().valueToTree(urcNodes + listOf(downlinkNode))
fun messageWithUrc(urcs: List<String> = listOf(), downlink: String = ""): JsonNode {
val downlinkString = """{"$DL_FIELD":"$downlink"}"""
val urcObjects =
(urcs + downlinkString).map { if (isJsonObject(it)) mapper.readTree(it) else mapper.readValue(""""$it"""") }
val arrayNode = mapper.createArrayNode().addAll(urcObjects)

val message = messageTemplate()
message.replace(URC_FIELD, urcFieldValue)
message.replace(URC_FIELD, arrayNode)

return message
}

private fun isJsonObject(it: String) = it.startsWith("{") || it.startsWith("[")
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import org.gxf.crestdeviceservice.model.AlarmsInfo

object TestConstants {
const val DEVICE_ID = "device-id"
val CORRELATION_ID = UUID.randomUUID()
val timestamp = Instant.now()
val CORRELATION_ID: UUID = UUID.randomUUID()
val timestamp: Instant = Instant.now()

const val DEVICE_MESSAGE_TOPIC = "device-message"
const val COMMAND_FEEDBACK_TOPIC = "command-feedback"
Expand All @@ -29,8 +29,7 @@ object TestConstants {
const val ANALOG_ALARM_THRESHOLDS_MILLIBAR_PORT_4 = "4:0,1250,2500,3750,25"
const val ANALOG_ALARM_THRESHOLDS_PAYLOAD_PORT_4 = "AL7:0,500,1000,1500,10"

const val ALARMS_INFO_DOWNLINK =
"\"!INFO:ALARMS\", {\"AL0\":[0,1,0,1,0], \"AL1\":[0,0,0,0,0], \"AL6\":[0,500,1000,1500,10]}"
const val ALARMS_INFO_DOWNLINK = "!INFO:ALARMS"
val ALARMS_INFO =
AlarmsInfo(AL0 = listOf(0, 1, 0, 1, 0), AL1 = listOf(0, 0, 0, 0, 0), AL6 = listOf(0, 500, 1000, 1500, 10))
const val ALARMS_INFO_FEEDBACK = "{\"tamper\":[0,1,0,1,0],\"digital\":[0,0,0,0,0],\"3\":[0,1250,2500,3750,25]}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.justRun
import io.mockk.verify
import java.io.IOException
import java.util.stream.Stream
import org.assertj.core.api.Assertions.assertThat
import org.gxf.crestdeviceservice.CommandFactory
Expand All @@ -21,7 +20,6 @@ import org.gxf.crestdeviceservice.command.feedbackgenerator.InfoAlarmsFeedbackGe
import org.gxf.crestdeviceservice.command.service.AlarmsInfoService
import org.gxf.crestdeviceservice.command.service.CommandFeedbackService
import org.gxf.crestdeviceservice.command.service.CommandService
import org.gxf.crestdeviceservice.model.AlarmsInfo
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
Expand Down Expand Up @@ -59,7 +57,7 @@ class InfoAlarmsResultHandlerTest {

@Test
fun hasSucceeded() {
every { alarmsInfoService.getAlarmsInfo(any()) } returns AlarmsInfo()
every { alarmsInfoService.containsResult(any()) } returns true

val result = resultHandler.hasSucceeded(command, message)

Expand All @@ -68,7 +66,7 @@ class InfoAlarmsResultHandlerTest {

@Test
fun hasSucceededFails() {
every { alarmsInfoService.getAlarmsInfo(any()) } throws IOException()
every { alarmsInfoService.containsResult(any()) } returns false

val result = resultHandler.hasSucceeded(command, message)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,26 @@
// SPDX-License-Identifier: Apache-2.0
package org.gxf.crestdeviceservice.command.service

import io.mockk.impl.annotations.InjectMockKs
import io.mockk.junit5.MockKExtension
import java.util.stream.Stream
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.gxf.crestdeviceservice.MessageFactory
import org.gxf.crestdeviceservice.TestConstants.ALARMS_INFO
import org.gxf.crestdeviceservice.TestConstants.ALARMS_INFO_DOWNLINK
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource

@ExtendWith(MockKExtension::class)
class AlarmsInfoServiceTest {
@InjectMockKs lateinit var alarmsInfoService: AlarmsInfoService
private val alarmsInfoService = AlarmsInfoService()

@Test
fun getAlarmsInfo() {
val message = MessageFactory.messageWithUrc(listOf(), ALARMS_INFO_DOWNLINK)
val message =
MessageFactory.messageWithUrc(
listOf("""{"AL0":[0,1,0,1,0], "AL1":[0,0,0,0,0], "AL6":[0,500,1000,1500,10]}"""),
ALARMS_INFO_DOWNLINK,
)
val expected = ALARMS_INFO

val result = alarmsInfoService.getAlarmsInfo(message)

assertThat(result).isEqualTo(expected)
}

@ParameterizedTest
@MethodSource("incorrectDownlinks")
fun getAlarmsInfoThrowsException(downlink: String) {
val message = MessageFactory.messageWithUrc(listOf(), downlink)

assertThatThrownBy { alarmsInfoService.getAlarmsInfo(message) }.isInstanceOf(Exception::class.java)
}

companion object {
@JvmStatic
fun incorrectDownlinks(): Stream<Arguments> =
Stream.of(
Arguments.of(
"\"!INFO:ALARMS\", {\"AL\":[0,1,0,1,0], \"AL1\":[0,0,0,0,0], \"AL6\":[0,500,1000,1500,10]}"
),
Arguments.of("{\"AL0\":[0,1,0,1,0], \"AL1\":[0,0,0,0,0], \"AL6\":[0,500,1000,1500,10]}"),
Arguments.of("\"!INFO:ALARMS\""),
Arguments.of("\"!INFO:ALARMS\", {\"AL0\":0,1,0,1,0, \"AL1\":0,0,0,0,0, \"AL6\":0,500,1000,1500,10}"),
Arguments.of("\"!INFO:ALARMS\", \"AL0\":[0,1,0,1,0], \"AL1\":[0,0,0,0,0], \"AL6\":[0,500,1000,1500,10]"),
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-FileCopyrightText: Copyright Contributors to the GXF project
//
// SPDX-License-Identifier: Apache-2.0
package org.gxf.crestdeviceservice.model

import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class DeviceMessageTest {
private val mapper = jacksonObjectMapper()

private val jsonMessage =
"""
{
"ID": 867787050253370,
"URC": [
{
"DL": "!INFO:ALARMS"
},
{
"AL0": [ 1, 2, 3, 4, 5 ],
"AL1": [ 10, 20, 30, 40, 50 ],
"AL2": [ 100, 200, 300, 400, 500 ]
}
],
"FMC": 3
}
"""
.trimIndent()
private val deviceMessage = DeviceMessage(mapper.readTree(jsonMessage))

@Test
fun `should return DL command from URC`() {
assertThat(deviceMessage.getDownlinkCommand()).isEqualTo("!INFO:ALARMS")
}

@Test
fun `should return object containing AL2`() {
assertThat(deviceMessage.getUrcContainingField("AL2")).isNotNull
assertThat(deviceMessage.getUrcContainingField("AL2").findValue("AL1").map { it.intValue() })
.containsExactly(10, 20, 30, 40, 50)
}

@Test
fun `should return Fota Message Counter`() {
assertThat(deviceMessage.getFotaMessageCounter()).isEqualTo(3)
}
}

0 comments on commit 3edfcfd

Please sign in to comment.