Code-House / bacnet4j-wrapper

Simple facade for bacnet4j api.
GNU General Public License v3.0
48 stars 20 forks source link

Problem to set a value #13

Closed aussichicken closed 4 years ago

aussichicken commented 4 years ago

Hi, i'm new to programming and try to work with bacnet4j-wrapper for my master thesis. I want to set a value (Analog Output) to a new value. For setPropertyValue i have to set a property and value, this works. But i don't know, what i have to do with the JavaToBacNetConverter. If I set 'null' i get following error:

17:47:06.305 [BACnet4J transport for device 12] ERROR c.s.b.t.DefaultTransport - Error during send: OutgoingConfirmed [maxAPDULengthAccepted=50, segmentationSupported=no-segmentation, service=WritePropertyRequest [objectIdentifier=analog-output 3, propertyIdentifier=present-value, propertyArrayIndex=null, priority=null, propertyValue=null], consumer=com.serotonin.bacnet4j.transport.ServiceFutureImpl@70357cea, address=Address [networkNumber=0, macAddress=[c0,a8,b2,14,e7,b0]], linkService=null]
java.lang.NullPointerException: null
    at com.serotonin.bacnet4j.type.Encodable.writeANY(Encodable.java:539)
    at com.serotonin.bacnet4j.service.confirmed.WritePropertyRequest.write(WritePropertyRequest.java:76)
    at com.serotonin.bacnet4j.transport.DefaultTransport$OutgoingConfirmed.sendImpl(DefaultTransport.java:369)
    at com.serotonin.bacnet4j.transport.DefaultTransport$Outgoing.send(DefaultTransport.java:336)
    at com.serotonin.bacnet4j.transport.DefaultTransport.run(DefaultTransport.java:486)
    at java.base/java.lang.Thread.run(Thread.java:830)
17:47:06.305 [BACnet4J transport for device 12] ERROR c.s.b.t.DefaultTransport - Original send stack
java.lang.Exception: null
    at com.serotonin.bacnet4j.transport.DefaultTransport.send(DefaultTransport.java:292)
    at com.serotonin.bacnet4j.transport.DefaultTransport.send(DefaultTransport.java:283)
    at com.serotonin.bacnet4j.LocalDevice.send(LocalDevice.java:998)
    at org.code_house.bacnet4j.wrapper.api.BacNetClientBase.setPropertyValue(BacNetClientBase.java:163)
    at org.code_house.bacnet4j.wrapper.api.BacNetClientBase.setPropertyValue(BacNetClientBase.java:153)
    at org.code_house.bacnet4j.wrapper.api.BacNetClientBase.setPropertyValue(BacNetClientBase.java:148)
    at LichtsteuerungOhneBasyc.BACnet_Test.find(BACnet_Test.java:62)
    at LichtsteuerungOhneBasyc.BACnet_Test.main(BACnet_Test.java:71)

My complete code is:

package LichtsteuerungOhneBasyc;

import org.code_house.bacnet4j.wrapper.api.BacNetClient;
import org.code_house.bacnet4j.wrapper.api.BypassBacnetConverter;
import org.code_house.bacnet4j.wrapper.api.BypassJavaConverter;
import org.code_house.bacnet4j.wrapper.api.Device;
import org.code_house.bacnet4j.wrapper.api.Property;
import org.code_house.bacnet4j.wrapper.ip.BacNetIpClient;
import org.code_house.bacnet4j.wrapper.api.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.serotonin.bacnet4j.type.enumerated.ObjectType;
import com.serotonin.bacnet4j.type.primitive.Real;
import com.serotonin.bacnet4j.type.Encodable;

import java.util.List;
import java.util.Set;

public class BACnet_Test {

    String mac = "[B@1df82230";
    public byte[] macAdresse = mac.getBytes() ;
    Device testDevice;
    Property propAAZone1;
    Encodable test;

    private static final Logger log = LoggerFactory.getLogger( BACnet_Test.class );

        public BACnet_Test() {
        }

        void find() {
            String ip = "192.168.178.20";           //"139.6.140.189";
            String broadcast = "255.255.255.255";
            int deviceId = 12;
            BacNetClient client;
            client = new BacNetIpClient(ip,broadcast, deviceId);
            client.start();
            Set<Device> devices = client.discoverDevices(1000); // given number is timeout in millis
            log.info("Found: {} devices", devices.size());
            for (Device device : devices) {
            testDevice = device;                
            macAdresse = testDevice.getAddress();
            }   

            Device gateway = new Device (400001, macAdresse, 0);
            for (Property property : client.getDeviceProperties(gateway)) {
                System.out.println("Start: " + macAdresse + " :Ende");
                System.out.println("Test erfolgreich");   
                System.out.println(property.getDevice() + "; " + property.getName() + "; " + property.getId());
                }

            Property propAAZone1 = new Property (testDevice, 3, Type.ANALOG_OUTPUT);
            int valueAAZone1 = 100;
            System.out.println("Alter Wert: " + client.getPropertyValue(propAAZone1, new BypassBacnetConverter()));
            client.setPropertyValue(propAAZone1, valueAAZone1, null);

            client.stop();
            System.out.println("Client stop");
        }

        public static void main(String[] args) {
            BACnet_Test test = new BACnet_Test();
            test.find();
        }
}

Can someone please help me?

My second question would be, how can I set a specific MAC address instead of using .getAddress()?

Thank you very much for your help.

Have a nice weekend

Dominik

splatch commented 4 years ago

Hey Dominik, There are two questions, I will try to address both as much as I can. Converters in get/set methods of client are responsible to transmit java value to bacnet4j (bacnet) primitive type. There is no default implementation for second as there might be additional logic necessary for specific devices or applications. JavaToBacnetConverter is parametrized type and takes a Java type name as type argument, meaning that you can create variants of converters for floats, decimals and so on, or one generic one handling all java.lang.Object instances.

FYI, the converter should not return a null, but a new com.serotonin.bacnet4j.type.primitive.Null. Wrapper doesn't do any additional wrapping or transformation thus it is shifted to a caller.

For second question - there is a org.code_house.bacnet4j.wrapper.ip.IpDevice#IpDevice(int, byte[], int) constructor which allows you to pass instanceId, mac and networkId. With this you will be able to work with pre-defined address without the need for calling discovery.

Best, Łukasz

aussichicken commented 4 years ago

Hi Lukasz, thank you for your quick response. I could solve the problem with the pre-defined address.

But I'm still struggeling with the converter. I want to change a Analog Value to a new value (integer). I created a class "JavaDoubleConverter" similar to the class "BypassBacnetConverter". When i use it, i still get an error:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class com.serotonin.bacnet4j.type.Encodable (java.lang.Integer is in module java.base of loader 'bootstrap'; com.serotonin.bacnet4j.type.Encodable is in unnamed module of loader 'app')

The class looks like this:

package BACnet;
import org.code_house.bacnet4j.wrapper.api.JavaToBacNetConverter;
import com.serotonin.bacnet4j.type.Encodable;

public class JavaDoubleConverter<T> implements JavaToBacNetConverter<Encodable> {
    @Override
    public Encodable toBacNet(Encodable value) {
        return value;
    }
}

If i don't misunderstand the error message, there's a problem to cast an integer in an encodable. Do you have an idea? Maybe an example from an other project.

Thank you very much.

Best Dominik

splatch commented 4 years ago

In your case, when you write the conversion is not a straight task (let say). You might have a Java type which can be mapped to couple of bacnet types (real, unsigned, signed etc) depending on object type you are trying to write to.

That's why when you have an Integer you have to convert it to an encodable instance. You can use "bypass" converter on the write path only if you create Encodable yourself (client.setProperty(prop, <encodable>, new BypassEncodableEncoder()), otherwise your encoder should implement necessary logic to map Java to bacnet4j primitive.

So your code should look more like this:

package BACnet;
import org.code_house.bacnet4j.wrapper.api.JavaToBacNetConverter;
import com.serotonin.bacnet4j.type.Encodable;
import com.serotonin.bacnet4j.type.primitive.Null;

public class JavaDoubleConverter implements JavaToBacNetConverter<Double> {
    @Override
    public Encodable toBacNet(Double value) {
                if (value == null) return new Null();
        return new Real(value.floatValue());
    }
}

public class JavaIntegerConverter implements JavaToBacNetConverter<Integer> {
    @Override
    public Encodable toBacNet(Integer value) {
                if (value == null) return new Null();
        return new SignedInteger(value);
    }
}

There are variants of primitives on bacnet4j such as Unsigned8, Unsigned16, Unsigned32, SignedInteger.

Let me know if this helps. Best luck, Łukasz

aussichicken commented 4 years ago

Hi Lukasz, the code for double works, the integer one not, but that's no problem.

Thank you very much for your help :)

Best Dominik

splatch commented 4 years ago

I believe this issue can be marked as solved. Please re-open it if you encounter any problems with writing values.