MangoAutomation / BACnet4J

BACnet/IP stack written in Java. Forked from http://sourceforge.net/projects/bacnet4j/
GNU General Public License v3.0
183 stars 110 forks source link

Impossible to configure BBMD? #68

Open Frozenlock opened 2 years ago

Frozenlock commented 2 years ago

As far as I can tell, it's impossible to configure the BBMD table of a local device with methods/functions.

Looking at the tests, it looks like a remote device is simulated in order to to configure each BBMD.

Is my understanding correct? Which approach should I take if I want to create a BBMD table by providing multiple hosts/ports?

envas commented 1 year ago

Yes, you understand well. BBMD is completely unusable with BACnet4J. If one wants to program a BBMD router, one must be able to populate the BDT table with data. This is typically done when restarting a BBMD router. How the BDT content is distributed across all BBMD routers is not specified in the BACnet specification. The BACnet best practice only says that the BDT content should be the same on all routers. Companies implement this in different ways - a database table, an INI file, a central server from which the router downloads data, there are various options. But they all have one thing in common - the BBMD router must be able to populate the contents of the BDT table after a reboot. This is not possible with BACnet4J, because all API functions for content manipulation (IpNetwork.readBDT and IpNetwork.writeBDT) are private and can only be called via BACnet services.

Frozenlock commented 1 year ago

Thank you for confirming my suspicions and providing a detailed answer!

terrypacker commented 1 year ago

@Frozenlock if you have any suggestions for us to expose the methods you require let us know. A simple PR would be helpful.

kishorevenki commented 1 year ago
envas commented 1 year ago

@kishorevenki thank you for the detailed explanation. I am developer, not engineer, and I'm not talking about commissioning. I'm looking for a way how to populate the BDT on configured BBMD devices after a reboot, when the BDT content was cleared. BACnet4J doesn't support NetworkPort object, doesn't have local API to write to BDT, and doesn't even insert its own IP address into BDT after initialisation. So after a reboot/power failure I get an empty BDT. And there is no method to reconstruct the content of the BDT locally. The only way is to use remote device and send write-broadcast-distribution-table message from outside. Which is practically nonsense, because I would have to have some kind of watchdog that would permanently scan the entire distributed network and immediately remotely reloads the BBMD router with configuration data when it restarts.

I see absolutely no reason why it would be dangerous to change the modifier of the Java methods readBDT and writeBDT from private to public. These methods are intended for programmers and cannot be used other than in an application.

terrypacker commented 1 year ago

@envas I'm not fully following what you need here yet. I thought you trying to use BACnet4J to develop a BBMD device but this last comment left me considering that you may trying to develop code that can register with other BBMD devices?

Mango can register with a BBMD device using BACnet4J so I could show you how that is done if this is all you need.

envas commented 1 year ago

@terrypacker Yes, I try to develop a BBMD device. This device has a web user interface, where I perform the commissioning. The commissioning form has a Save button, that persists the BDT content into an internal database. After reboot, I need to populate the local BDT content from the database to restore the functionality. And I do not see any API method how to write the BDT content. The BBMD test unit uses a remote device simulation to write into the BDT. I find it impractical to create a new virtual device just for this function.

I see that the BBMD basically works, I can register my device as a foreign device with a BBMD (what you probably means that Mango uses). Other direction works as well - I can activate BBMD after calling network.enableBBMD() and other devices can register on my device as foreign devices.

My question is how to reload the BDT content after device reboot, like this (symbolic code)

IpNetwork network = new IpNetworkBuilder().withXXXX.withYYYY.build();
DefaultTransport transport = new DefaultTransport(network);
LocalDevice device = new LocalDevice(device_id, transport);
// enable BBMD 
network.enableBBMD();

// once enabled, others can write BDT (tested from Yabe and BacEye), however, assuming
// the BDT content was already configured, I need to reload it from the database
readDatabaseAndWriteBDT();    // <- that's what I am looking for

// configure other device properties and initialize device
device.initialize();
terrypacker commented 1 year ago

Ok, yes that makes sense. Are you comfortable forking this repo and making the changes that you require? If so you could then make a Pull Request and we could verify and merge your changes back in. At the very least it would be a starting point for us to add this feature.

terrypacker commented 1 year ago

Ideally if you make the changes can you provide a test showing us how you would use this feature, that will give us a better understanding of why you want it and if there are other changes that would help you achieve this.

envas commented 1 year ago

OK, give me a bit time, I have another Pull Requests in the queue. I will finish my project and then make a summary.

kishorevenki commented 1 year ago

@envas - Now I understand your concerned which is valid.

envas commented 1 year ago

Before I started with the API for local reading and writing of BDT and FDT tables, I analyzed the code how AnnexJ is implemented in BACnet4J. I think it was probably implemented without enough analysis, maybe some quick solution for the needs of Mango Automation. Some functions don't work, some work only by accident. Unit tests with 127.x.x.x addresses in my opinion are not sufficient, because these addresses do not behave the same on different operating systems (I did all the tests on real site segments separated by a real router, monitored by Wireshark).

However, as the main problem I see the questionable implementation of class DatagramSocket. In the JavaDoc for this class it is stated

... In order to receive broadcast packets a DatagramSocket should be bound to the wildcard address. In some implementations, broadcast packets may also be received when a DatagramSocket is bound to a more specific address.

The problem is that on MacOS and especially on Linux (used in most embedded systems) the interface does not accept a broadcasts if DatagramSocket is bound to a specific address. On Windows it works. So if someone is testing on Windows, it doesn't mean that the code is bug-free! In practice, this means that if we want broadcasts to work on all systems, we have to use 0.0.0.0 as the bound address for BVLC. And here is the problem - this address is then used in BACnet4J for all other BVLC functions as local bind address, which leads to the fact that in practice these functions either don't work or work by accident - due to errors in the code or due to missing ACK/NAK test in the response.

As an example I can give IpNetwork.deleteForeignDeviceTableEntry(). If fdtEntry is 0.0.0.0, the entry will never be deleted from the FDT table on the BBMD router! The function pretends that everything is OK just because the answer is not tested at all! In fact, the BBMD router returns NAK (tested with Wireshark) and the entry in the FDT disappears later after the time to live is over.

public void deleteForeignDeviceTableEntry(final InetSocketAddress addr, final InetSocketAddress fdtEntry)
            throws BACnetException {
        final ByteQueue queue = new ByteQueue();
        queue.push(BVLC_TYPE);
        queue.push(0x08); // Delete foreign device table entry
        queue.pushU2B(0xA); // Length
        pushISA(queue, fdtEntry); // 
        sendPacket(addr, queue.popAll());
    }

There are several similar errors in the code. Solving the problem of receiving broadcasts at specific addresses is not easy, many developer suggest creating two DatagramSockets, one specific for sending messages and another one with a wildcard address for receiving broadcasts. It's a topic for discussion.

Since we own the license for commercial use of BACnet4J, I have made some changes to our BACnet4J fork to make BBMD working with InetSocketAddress=0.0.0.0.0. However, because I think the overall BBMD solution is not optimal, I am not going to publish these changes as a PR. First of all I would recommend cleansing the BACnet4J code:

Currently I consider BBMD at BACnet4J to be non-functional, or only very limited functional.

devaskim commented 1 year ago

Are there any plans to let configuring BDT with public library's API ?

@envas Did you solve the implementation issue and how?