bacnet inside docker container

330 views Asked by At

I want to run my bacnet application in a docker launched with docker compose. The host machine is on the bacnet network. I would like to be able to discover the bacnet network in my application. My application is unable to send the broadcast request required to discover the bacnet network because the docker hosting my application is in the subnet created by docker.

I have tried many things:

  • trying to run docker on the host network (network_mode: host) => the main problem is that I can no longer expose my application's port
  • play with the different settings offered by my bacnet library (Bacnet4J)

Perhaps it is essential to set up a BBMD?

If anyone has ever run a bacnet application in a docker that runs on the bacnet network, I'd be really interested in a bit of help setting this up.

Here is the architecture I have :

enter image description here

1

There are 1 answers

0
DennisVM-D2i On

It is not strictly essential to have a BBMD; BBMD's are no longer being advocated for use since Bn/SC, and (UDPv4) Broadcasts are not strictly supported by IPv6 - you have to use a Multicast group to emulate them.

The important thing to be sure of is that you have request-response connectivity between the (BACnet) Client & Server - ideally for both (ICMP) ping (/'Echo Request') and BACnet/IP communication.

If you know the address details for your (BACnet) server, then you should be able to target it directly with 'YABE' & 'VTS', if not also with 'BACnet4j' too.

For example, using the NodeJS 'node-bacnet' package; the directed/'unicast' (/non-'broadcast') approach - reading the 'ObjId' from server device '127.1.2.2:47810' having a DOIN (Device Object Instance Number) of '321':

const bacnet = require('node-bacnet');

const client = new bacnet( { "port": 47816 });

client.whoIs("127.1.2.2:47810");

client.readProperty('127.1.2.2:47810', { "type": 8, "instance": 321 }, 75, (err, value) => {

    console.log('value: ', value);
    console.log('value: ', value. Values[0].value);
});

Output:

value:  {
  len: 14,
  objectId: { type: 8, instance: 391 },
  property: { id: 75, index: 4294967295 },
  values: [ { type: 12, value: [Object] } ]
}
value:  { type: 8, instance: 391 }

(Alongside ping, you can use 'Wireshark' and/or Windows 'netsh' for checking the network connectivity - but check your firewall rules too.)

...

I've not messed with Java for a long time, and have never tried using 'BACnet4J' until today - and have found it quite hard to get going with, but here's an example of me finding a (target) device that I know about (via a directed/'unicast' route), rather than via 'Who-Is'/'broadcast' (- IPv4 address '192.168.1.7' & port # '47810'/0xBAC2 with a DOIN value of '999'); I haven't tested the 'iAmReceived' code / I borrowed it from elsewhere :

// cls & java -classpath Lib\bacnet4j-3.2.2.jar;Lib\slf4j-api-1.7.9.jar;Lib\commons-lang3-3.12.0.jar Main.java

// https://github.com/MangoAutomation/BACnet4J/tree/master/release/3.2.2/
//    "Download" button - https://github.com/MangoAutomation/BACnet4J/tree/master/release/3.2.2/bacnet4j-3.2.2.jar

// https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.9/
//     https://repo1.maven.org/maven2/org/slf4j/slf4j-api/1.7.9/slf4j-api-1.7.9.jar

// https://commons.apache.org/proper/commons-lang/download_lang.cgi/
//     https://dlcdn.apache.org//commons/lang/binaries/commons-lang3-3.12.0-bin.zip

//  DVM  ;)

import java.util.List;

import com.serotonin.bacnet4j.LocalDevice;
import com.serotonin.bacnet4j.RemoteDevice;
import com.serotonin.bacnet4j.event.DeviceEventAdapter;
import com.serotonin.bacnet4j.exception.BACnetException;
import com.serotonin.bacnet4j.exception.BACnetTimeoutException;
import com.serotonin.bacnet4j.npdu.ip.IpNetwork;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkBuilder;
import com.serotonin.bacnet4j.npdu.ip.IpNetworkUtils;
import com.serotonin.bacnet4j.npdu.Network;
import com.serotonin.bacnet4j.service.acknowledgement.ReadPropertyAck;
import com.serotonin.bacnet4j.service.confirmed.ReadPropertyRequest;
import com.serotonin.bacnet4j.transport.DefaultTransport;
import com.serotonin.bacnet4j.transport.Transport;
import com.serotonin.bacnet4j.type.constructed.Address;
import com.serotonin.bacnet4j.type.constructed.SequenceOf;
import com.serotonin.bacnet4j.type.constructed.ServicesSupported;
import com.serotonin.bacnet4j.type.enumerated.PropertyIdentifier;
import com.serotonin.bacnet4j.type.enumerated.Segmentation;
import com.serotonin.bacnet4j.type.primitive.ObjectIdentifier;
import com.serotonin.bacnet4j.type.primitive.OctetString;
import com.serotonin.bacnet4j.type.primitive.Unsigned16;
import com.serotonin.bacnet4j.type.primitive.UnsignedInteger;
import com.serotonin.bacnet4j.util.RequestUtils;

class Main
{
    private static LocalDevice localDevice;

    public static void main(String[] args)
        throws Exception
    {
        System.out.println();

        // CONFIGURE:  For your own NIC/network card
        // (- UDPv4 'Broadcast' address)
        IpNetwork network = new IpNetworkBuilder().port(47814).subnetMask("192.168.1.255").build();
        // Deprecated (- '-Xlint:deprecation')
        //IpNetwork network = new IpNetwork("192.168.1.255", 47814);

        Transport transport = new DefaultTransport(network);
        // transport.setTimeout(15000);
        // transport.setSegTimeout(15000);

        localDevice = new LocalDevice(1234, transport);

        try
        {
            localDevice.initialize();

            localDevice.getEventHandler().addListener(new Listener());
            // localDevice.sendLocalBroadcast(new WhoIsRequest());
            // localDevice.sendGlobalBroadcast(new WhoIsRequest());

            // Using NIC/IP End-Point '192.168.1.7'/'192.168.1.7:47814'
            // (- 'c0,a8,1,7,ba,c6'); using the same IP address as
            // the target device but with a different port # -
            // '47814' vs/to '47810'
            for (Address addr : localDevice.getAllLocalAddresses())
            {
                System.out.println("\t" + addr);
            }

            System.out.println();

            // CONFIGURE:  For your own device
            Address targAddr = IpNetworkUtils.toAddress("192.168.1.7", 47810);

            System.out.println("TargAddr = '" + targAddr.toString() + "'");
            System.out.println();

            // localDevice.addRemoteDevice(targAddr, 999);
            RemoteDevice targDevice = localDevice.findRemoteDevice(targAddr, 999);
            if (targDevice != null)
            {
                System.out.println(targDevice.toString());
                System.out.println();

                getExtendedDeviceInformation(targDevice);

                System.out.println("Object-Name = '" + targDevice.getName() + "'");
                System.out.println();

                System.out.println("Vendor-Identifier = '" + targDevice.getVendorId() + "'");
                System.out.println("Vendor-Name = '" + targDevice.getVendorName() + "'");
                System.out.println();

                System.out.println("Protocol-Version = '" + targDevice.getProtocolVersion() + "'");
                System.out.println("Protocol-Revision = '" + targDevice.getProtocolRevision() + "'");

                System.out.println();
                System.out.println("Services-Supported = '" + targDevice.getServicesSupported() + "'");
            }

            Thread.sleep(3000);
        }
        catch (BACnetTimeoutException exc)
        {
            System.out.flush();

            System.err.println(exc.getMessage());
            System.err.flush();
        }
        catch (BACnetException exc)
        {
            System.out.flush();

            System.err.println(exc.getMessage());
            System.err.flush();
        }
        catch (Exception exc)
        {
            System.out.flush();

            System.err.println(exc.getMessage());
            System.err.flush();
        }
        finally
        {
            localDevice.terminate();

            System.out.println();

            System.out.flush();
        }
    }

    static class Listener extends DeviceEventAdapter
    {
        // I haven't tested the 'iAmReceived()' code/method - I borrowed it from elsewhere
        @Override
        public void iAmReceived(RemoteDevice d)
        {
            try
            {
                System.out.println("IAm received from " + d);
                System.out.println("Segmentation: " + d.getSegmentationSupported());
                d.setSegmentationSupported(Segmentation.noSegmentation);

                Address a = new Address(new Unsigned16(0), new OctetString(new byte[] { (byte) 0xc0, (byte) 0xa8, 0x1,
                        0x5, (byte) 0xba, (byte) 0xc0 }));

                System.out.println("Equals: " + a.equals(d.getAddress()));
                getExtendedDeviceInformation(d);
                System.out.println("Done getting extended information");

                List oids = ((SequenceOf) RequestUtils.sendReadPropertyAllowNull(localDevice, d,
                        d.getObjectIdentifier(), PropertyIdentifier.objectList)).getValues();
                System.out.println(oids);
            }
            catch (BACnetException e)
            {
                e.printStackTrace();
            }
        }
    }

    static void getExtendedDeviceInformation(RemoteDevice d)
        throws BACnetException
    {
        ObjectIdentifier oid = d.getObjectIdentifier();

        ReadPropertyAck ack;

        // -

        System.out.println("ExtDevInfo:  Getting 'Object-Name' .  .   .");

        ack =
            (ReadPropertyAck)
                (
                    localDevice.send(
                        d,
                        new ReadPropertyRequest(
                            oid,
                            PropertyIdentifier.objectName)).get()
                );

        d.setName(ack.getValue().toString());

        // -

        System.out.println("ExtDevInfo:  Getting 'Vendor-Identifier' .  .   .");

        ack =
            (ReadPropertyAck)
                (
                    localDevice.send(
                        d,
                        new ReadPropertyRequest(
                            oid,
                            PropertyIdentifier.vendorIdentifier)).get()
                );

        d.setVendorId(((Unsigned16) ack.getValue()).intValue());

        // -

        System.out.println("ExtDevInfo:  Getting 'Vendor-Name' .  .   .");

        ack =
            (ReadPropertyAck)
                (
                    localDevice.send(
                        d,
                        new ReadPropertyRequest(
                            oid,
                            PropertyIdentifier.vendorName)).get()
                );

        d.setVendorName(ack.getValue().toString());


        // -

        System.out.println("ExtDevInfo:  Getting 'Protocol-Version' .  .   .");

        ack =
            (ReadPropertyAck)
                (
                    localDevice.send(
                        d,
                        new ReadPropertyRequest(
                            oid,
                            PropertyIdentifier.protocolVersion)).get()
                );

        d.setProtocolVersion((UnsignedInteger) ack.getValue());

        // -

        System.out.println("ExtDevInfo:  Getting 'Protocol-Revision' .  .   .");

        ack =
            (ReadPropertyAck)
                (
                    localDevice.send(
                        d,
                        new ReadPropertyRequest(
                            oid,
                            PropertyIdentifier.protocolRevision)).get()
                );

        d.setProtocolRevision((UnsignedInteger) ack.getValue());

        // -

        // Get the device's supported services
        System.out.println("ExtDevInfo:  Getting 'Protocol-Services-Supported' .  .   .");

        ack =
            (ReadPropertyAck)
                (
                    localDevice.send(
                        d,
                        new ReadPropertyRequest(
                            oid,
                            PropertyIdentifier.protocolServicesSupported))
                ).get();

        d.setServicesSupported((ServicesSupported) ack.getValue());

        // -

        System.out.println();
    }
}

Folders & files:

│   Main.java
│   Run.cmd
│
├───.vscode
│       launch.json
│
└───Lib
        bacnet4j-3.2.2.jar
        commons-lang3-3.12.0.jar
        slf4j-api-1.7.9.jar

My run (Windows 'Command Prompt') 'CMD' script (/Batch file):

java -classpath Lib\bacnet4j-3.2.2.jar;Lib\slf4j-api-1.7.9.jar;Lib\commons-lang3-3.12.0.jar Main.java

Environment:

'Windows 10 Pro' - '22H2' - '19045.3086'

openjdk version "17.0.7" 2023-04-18 LTS
OpenJDK Runtime Environment Corretto-17.0.7.7.1 (build 17.0.7+7-LTS)
OpenJDK 64-Bit Server VM Corretto-17.0.7.7.1 (build 17.0.7+7-LTS, mixed mode, sharing)

Output:

        Address [networkNumber=0, macAddress=[c0,a8,1,7,ba,c6]]
        Address [networkNumber=0, macAddress=[c0,a8,38,1,ba,c6]]
        Address [networkNumber=0, macAddress=[ac,1b,c0,1,ba,c6]]
        Address [networkNumber=0, macAddress=[ac,12,b0,1,ba,c6]]

TargAddr = 'Address [networkNumber=0, macAddress=[c0,a8,1,7,ba,c2]]'

RemoteDevice(instanceNumber=999, address=Address [networkNumber=0, macAddress=[c0,a8,1,7,ba,c2]])

ExtDevInfo:  Getting 'Object-Name' .  .   .
ExtDevInfo:  Getting 'Vendor-Identifier' .  .   .
ExtDevInfo:  Getting 'Vendor-Name' .  .   .
ExtDevInfo:  Getting 'Protocol-Version' .  .   .
ExtDevInfo:  Getting 'Protocol-Revision' .  .   .
ExtDevInfo:  Getting 'Protocol-Services-Supported' .  .   .

Object-Name = 'DVM BMD'

Vendor-Identifier = '24473'
Vendor-Name = 'DVM  ;)'

Protocol-Version = '1'
Protocol-Revision = '7'

Services-Supported = '[true, true, true, true, true, true, true, true, true, true, true, true, true, false, true, true, true, true, false, false, true, false, false, false, false, false, true, true, true, true, false, false, true, true, true, true, true, false, true, true, false]'

Hope this helps.   ;D