MangoAutomation/BACnet4J

Impossible to configure BBMD?

Frozenlock opened this issue · 13 comments

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

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.

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

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

  • As the name BBMD - BACnet Broadcast Management Device indicates, the BBMD is for managing network messages in BACnet over multiple IP LAN.
  • Hence the required configuration is to be handled by the comissioning Engineer or operators at the site. The configuration will also vary as per the site requirement.
  • There is no requirement or suggestion that all the BBMD in multiple LAN has the same data in their BDT. Once again this would be decided based on the site requirement.
  • Normally a device claiming BBMD can have checkbox or equiv to enable or disable BBMD Functionality. Once enabled, the local device should add its IP Address with Subnet mask (prefix of 32) into its BDT Table. Subsequently BBMD related services like ReadBDT, WriteBDT service should be enabled. When other BBMD devices read the BDT of the local device, at the outset it will its IP address only.
  • For the devices supporting Protocol Revision <17, uses the write BDT to add the IP details of other BBMD devices.
  • For the devices supporting > revision 17 uses Network Port object to configure these details wherein simple Write Property or Read Property is used along with BBMD services.
  • Once the BBMD feature is disabled, BDT table shall be empty and should return NAK for ReadBDT and WriteBDT.
  • It may not be advisable to expose a method to add BDT without the above steps which may be cause catastrophic bombardment of network messages in the IP LAN. We have come across situation wherein due to incorrect configuration of BBMD, multiple Airport terminals had to close the operation including flight landing and take off until the issues were resolved.
envas commented

@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.

@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

@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();

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.

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

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

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

envas commented

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:

  • create a NetworkPort object (and delete the terrible static classes IpNetwork.FDTEntry and IpNetworkBDTEntry).
    Obviously someone has already started with this, there are types FDTEntry and BDTEntry as BaseType subclasses,
    but the work remains unfinished
  • it is necessary to solve the problem of specific InetSocketAddress registration vs. wildcard address registration for DatagramSocket
  • Design the BBMD implementation so, that it works with IPv4 and IPv6.
  • Complete BVLC messages so that they work and test ACK/NAK responses.

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

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

@envas Did you solve the implementation issue and how?