Skip to content
This repository has been archived by the owner on Jun 14, 2024. It is now read-only.

Commit

Permalink
#14 Validate single COV message.
Browse files Browse the repository at this point in the history
  • Loading branch information
baardl committed Aug 25, 2020
1 parent f756488 commit 433429e
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 9 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
<version>1.2.3</version>
<scope>provided</scope>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
Expand All @@ -121,6 +123,11 @@
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>

</dependencies>

Expand Down
6 changes: 3 additions & 3 deletions src/main/java/no/entra/bacnet/json/Bacnet2Json.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,13 @@ static JSONObject addServiceInfo(JSONObject bacnetJson, Bvlc bvlc, Npdu npdu, Se
bacnetJson.put(OBSERVATION, observationJson);
break;
case ConfirmedRequest:
ConfigurationRequest confirmedRequest = tryToUnderstandConfirmedRequest(service);
BacnetMessage confirmedRequest = tryToUnderstandConfirmedRequest(service);
observationJson = buildObservationJson(bvlc, npdu, confirmedRequest);
bacnetJson.put(CONFIGURATION_REQUEST, observationJson);
break;
case UnconfirmedRequest:
BacnetMessage bacnetMessage = tryToUnderstandUnconfirmedRequest(service);
observationJson = buildObservationJson(bvlc, npdu, bacnetMessage);
BacnetMessage unconfirmedRequest = tryToUnderstandUnconfirmedRequest(service);
observationJson = buildObservationJson(bvlc, npdu, unconfirmedRequest);
bacnetJson.put(CONFIGURATION_REQUEST, observationJson);
break;
default:
Expand Down
1 change: 1 addition & 0 deletions src/main/java/no/entra/bacnet/json/Observation.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ public String toString() {
'}';
}

//FIXME
public String toJson() {
JSONObject json = new JSONObject();
json.put(ID, id);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/no/entra/bacnet/json/ObservationList.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public JSONObject asJsonObject() {
JSONObject json = new JSONObject();
JSONArray observationsJson = new JSONArray();
for (Observation observation : observations) {
observationsJson.put(observation.toJson());
observationsJson.put(observation.asJsonObject());
}
json.put("observations", observationsJson);

Expand Down
127 changes: 127 additions & 0 deletions src/main/java/no/entra/bacnet/json/observation/ObservationParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
import no.entra.bacnet.json.Observation;
import no.entra.bacnet.json.ObservationList;
import no.entra.bacnet.json.Source;
import no.entra.bacnet.json.objects.ObjectId;
import no.entra.bacnet.json.objects.PropertyIdentifier;
import no.entra.bacnet.json.objects.SDContextTag;
import no.entra.bacnet.json.parser.ObjectIdParser;
import no.entra.bacnet.json.parser.ObjectIdParserResult;
import no.entra.bacnet.json.reader.OctetReader;
import org.slf4j.Logger;

Expand Down Expand Up @@ -88,6 +92,129 @@ public static ObservationList buildChangeOfValueObservation(String changeOfValue
return new ObservationList(observations);
}

public static ObservationList parseConfirmedCOVNotification(String changeOfValueHexString) {

/*
1. Subscriber Process Identifier (SD Context Tag 0, Length 1)
2. Initating Device Identifier (SD Context Tag 1, Length 4)
3. Monitored Object Idenfiter (SD Context Tag 2, Length 4)
4. Time remaining (SD Context Tag 3, Length 1)
5. List of values
Example hex: 0f0109121c020200252c0000000039004e095519012e4441a4cccd2f4f
*/
List<Observation> observations = new ArrayList<>();
Map<String, Object> properties = new HashMap<>();

OctetReader covReader = new OctetReader(changeOfValueHexString);
Octet invokeIdOctet = covReader.next(); //0f == 15
Octet serviceChoice = covReader.next(); //05 == 05
//ProcessIdentifier
Integer subscriberProcessId = null;
Octet subscriberProcessLength = covReader.next(); //09 == length 1
if (subscriberProcessLength.toString().equals("09")) {
Octet subscriberProcessIdOctet = covReader.next();
subscriberProcessId = toInt(subscriberProcessIdOctet.toString());
} else {
//TODO find processId longer than one bit.
throw new IllegalArgumentException("SubscriberProcessId not implemented for integer > 15");
}
//DeviceIdentifier 1c02020025
Octet deviceIdLengthKey = covReader.next(); //1c
ObjectId deviceId = null;
if (deviceIdLengthKey.toString().equals("1c")) {
String objectIdHex = "1c" + covReader.next(4);
ObjectIdParserResult<ObjectId> result = ObjectIdParser.parse(objectIdHex);
deviceId = result.getParsedObject();
} else {
//TODO find DeviceIdentifier
throw new IllegalArgumentException("ObjectIdentifier different key than 1c is not implemented yet.");
}
//ObjectIdentifier 1c00000000
Octet objectIdLengthKey = covReader.next(); //2c
ObjectId objectIdentifier = null;
if (objectIdLengthKey.toString().equals("2c")) {
//length is 4
String objectIdHex = "2c" + covReader.next(4);
ObjectIdParserResult<ObjectId> result = ObjectIdParser.parse(objectIdHex);
objectIdentifier = result.getParsedObject();
} else {
//TODO find ObjcetIdentifier
throw new IllegalArgumentException("ObjectIdentifier different key than 2c is not implemented yet.");
}

//Time remaining
int timeRemainingSec = 0;
Octet contextTag = covReader.next();
if (SDContextTag.fromOctet(contextTag) == SDContextTag.TimeStamp) {
if (contextTag.getSecondNibble() == 9) {
//read one octet
Octet timeRemainingOctet = covReader.next();
timeRemainingSec = toInt(timeRemainingOctet);
}
}

//List of values


//Confirmed notifications 2901
// String devicdId = "TODO"; //TODO #4 find source device from APDU, NPDU or IP address/port
// Octet intiatingDeviceidKey = covReader.next();
// Octet[] initiatingDeviceIdValue = covReader.nextOctets(4);
// Octet monitoredDeviceIdKey = covReader.next();
// Octet[] monitoredDeviceIdValue = covReader.nextOctets(4);
// Octet timeRemainingKey = covReader.next();
// Octet timeRemainingValue = covReader.next();

String resultListHexString = covReader.unprocessedHexString();
resultListHexString = filterResultList(resultListHexString);
try {
Octet startList = covReader.next();
while (resultListHexString != null && resultListHexString.length() >= 2) {
Octet contextTagKey = covReader.next();
PropertyIdentifier propertyId = null;
if (contextTagKey != null && contextTagKey.equals(new Octet("09"))) {
Octet contextTagValue = covReader.next();
propertyId = PropertyIdentifier.fromPropertyIdentifierHex(contextTagValue.toString());
}
if (propertyId != null) {
Octet valueTagKey = covReader.next();
Octet propertyIdKey = covReader.next();
char lengthChar = propertyIdKey.getSecondNibble();
int length = toInt(lengthChar);
String value = covReader.next(length);
Octet valueTagEndKey = covReader.next();
properties.put(propertyId.name(), value);
}
resultListHexString = covReader.unprocessedHexString();
log.trace("unprocessed: {}", resultListHexString);
}

} catch (Exception e) {
log.debug("Failed to build ReadAccessResult from {}. Reason: {}", resultListHexString, e.getMessage());
}

if (properties != null && properties.size() > 0) {
for (String key : properties.keySet()) {
Object value = properties.get(key);
String observationId = null;
Source source = null;
String sourceInstanceNumber = null;
if (deviceId != null) {
sourceInstanceNumber = deviceId.getInstanceNumber();
} else {
sourceInstanceNumber = "TODO";
}
source = new Source(deviceId.getInstanceNumber(), objectIdentifier.getInstanceNumber());
Observation observation = new Observation(observationId, source, value, key);
observation.setName(key);
observations.add(observation);
}
}


return new ObservationList(observations);
}

static String filterResultList(String hexString) {
int listStartPos = hexString.indexOf(PD_OPENING_TAG_4);
int listEndPos = hexString.indexOf(PD_CLOSING_TAG_4);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package no.entra.bacnet.json.services;

import no.entra.bacnet.Octet;
import no.entra.bacnet.json.ConfigurationRequest;
import no.entra.bacnet.json.BacnetMessage;
import no.entra.bacnet.json.objects.PduType;
import no.entra.bacnet.json.observation.ObservationParser;
import org.slf4j.Logger;

import static no.entra.bacnet.json.configuration.ConfigurationParser.*;
Expand All @@ -14,8 +15,8 @@ public ConfirmedService(PduType pduType, Octet serviceChoice) {
super(pduType, ConfirmedServiceChoice.fromOctet(serviceChoice));
}

public static ConfigurationRequest tryToUnderstandConfirmedRequest(Service service) {
ConfigurationRequest configuration = null;
public static BacnetMessage tryToUnderstandConfirmedRequest(Service service) {
BacnetMessage configuration = null;
if (service == null) {
return null;
}
Expand Down Expand Up @@ -62,6 +63,11 @@ public static ConfigurationRequest tryToUnderstandConfirmedRequest(Service servi
case AtomicWriteFile:
log.trace("Ignoring for now: {}", confirmedServiceChoice);
break;
case SubscribeCov:
log.trace("Is SubscribeCov aka ConfirmedCOVNotification. hexString: {}", service.getUnprocessedHexString());
String covApduHexString = service.getUnprocessedHexString();
configuration = ObservationParser.parseConfirmedCOVNotification(covApduHexString);
break;
default:
log.trace("I do not know how to parse this service: {}", service);
}
Expand Down
24 changes: 24 additions & 0 deletions src/test/java/no/entra/bacnet/json/parser/ObjectIdParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ void parseDevice517() {

}

@Test
void parseDevice131109() {
String device131109 = "1c02020025";
ObjectIdParserResult<ObjectId> result = ObjectIdParser.parse(device131109);
assertNotNull(result);
assertNotNull(result.getParsedObject());
assertTrue(result.getParsedObject() instanceof ObjectId);
assertEquals(5, result.getNumberOfOctetsRead());
assertEquals(ObjectType.Device, result.getParsedObject().getObjectType());
assertEquals("131109", result.getParsedObject().getInstanceNumber());
}

@Test
void parseAnalogInput0() {
String device131109 = "2c00000000";
ObjectIdParserResult<ObjectId> result = ObjectIdParser.parse(device131109);
assertNotNull(result);
assertNotNull(result.getParsedObject());
assertTrue(result.getParsedObject() instanceof ObjectId);
assertEquals(5, result.getNumberOfOctetsRead());
assertEquals(ObjectType.AnalogInput, result.getParsedObject().getObjectType());
assertEquals("0", result.getParsedObject().getInstanceNumber());
}

@Test
void findObjectTypeIntTest() {
String objectTypeAsBitString = "00000010000000000000001000000101";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.entra.bacnet.json.segmentation;

import no.entra.bacnet.json.BacnetMessage;
import no.entra.bacnet.json.ConfigurationRequest;
import no.entra.bacnet.json.bvlc.BvlcParser;
import no.entra.bacnet.json.bvlc.BvlcResult;
Expand Down Expand Up @@ -46,8 +47,9 @@ void confirmedRequestReadProperty() {
assertEquals(55, service.getInvokeId());
// get properties
assertTrue(service instanceof ConfirmedService);
ConfigurationRequest request = ConfirmedService.tryToUnderstandConfirmedRequest(service);
BacnetMessage request = ConfirmedService.tryToUnderstandConfirmedRequest(service);
assertNotNull(request);
assertTrue(request instanceof ConfigurationRequest);
//objectIdentifier, device, 516
//property-identifier, object-list (76)
}
Expand Down
Loading

0 comments on commit 433429e

Please sign in to comment.