Menu

#76 make simple read request app

release
open
nobody
None
1
2024-01-27
2023-12-30
No

Im in the src directory of the project. Am a bit of a begineer here...

Could someone give me a tip? This is my main.c where I am experimenting with creating a read request app with some hard coded values to start with. This is my main.c:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "bacnet/bacdef.h"
#include "bacnet/bacdcode.h"
#include "bacnet/bacenum.h"
#include "bacnet/bacapp.h"
#include "bacnet/apdu.h"
#include "bacnet/npdu.h"
#include "bacnet/datalink/datalink.h"
#include "bacnet/whois.h"
#include "bacnet/iam.h"
#include "bacnet/rpm.h"

#define TARGET_DEVICE_INSTANCE 201201
#define TARGET_OBJECT_TYPE     OBJECT_ANALOG_INPUT
#define TARGET_OBJECT_INSTANCE 1
#define TARGET_PROPERTY_ID     PROP_PRESENT_VALUE

// Make Error, Abort, and Reject Handlers here...

// Callback function for Read Property Acknowledgement
void MyReadPropertyCallback(
    uint8_t *service_request,
    uint16_t service_len,
    BACNET_ADDRESS *src,
    BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data) {
    printf("Read Property Callback\n");
}

int main() {
    BACNET_ADDRESS target;
    uint32_t device_instance;
    unsigned max_apdu = 0;
    unsigned timeout = 100; // ms
    unsigned retries = 3;
    uint8_t invoke_id = 0;

    // Initialize the BACnet stack
    Device_Init(NULL);
    dlenv_init();

    // Register the Read Property callback
    apdu_set_confirmed_ack_handler(SERVICE_CONFIRMED_READ_PROPERTY, MyReadPropertyCallback);

    // Send a WhoIs to discover devices on the network
    Send_WhoIs(-1, -1);

    // Wait for responses or a timeout
    for (int i = 0; i < 100; i++) {
        dlenv_maintenance();
    }

    // Assuming we know the device instance of the target
    device_instance = TARGET_DEVICE_INSTANCE;

    // Send the read property request
    invoke_id = Send_Read_Property_Request(
        device_instance,
        TARGET_OBJECT_TYPE,
        TARGET_OBJECT_INSTANCE,
        TARGET_PROPERTY_ID,
        BACNET_ARRAY_ALL
    );

    if (invoke_id > 0) {
        // Wait for the response or a timeout
        for (int i = 0; i < 100; i++) {
            dlenv_maintenance();
        }
    } else {
        // Error handling if sending the request fails
        fprintf(stderr, "Failed to send Read Property Request!\n");
    }

    return 0;
}

And Makefile:

# Makefile to build your BACnet Application using GCC

# Compiler
CC := gcc

# Compiler flags
CFLAGS := -g -I./ -I./bacnet -I./bacnet/basic -I./bacnet/basic/service -I./bacnet/basic/bbmd -I./bacnet/datalink

# Executable file name
EXECUTABLE := bacnet_app

# BACnet source directory
BACNET_SRC_DIR := ./bacnet

# Source files
SRCS := $(wildcard *.c) \
        $(wildcard $(BACNET_SRC_DIR)/*.c) \
        $(wildcard $(BACNET_SRC_DIR)/basic/*.c) \
        $(wildcard $(BACNET_SRC_DIR)/basic/service/*.c) \
        $(wildcard $(BACNET_SRC_DIR)/basic/bbmd/*.c) \
        $(wildcard $(BACNET_SRC_DIR)/datalink/*.c)

# Object files
OBJS := $(SRCS:.c=.o)

# Default target
all: $(EXECUTABLE)

# Linking the executable
$(EXECUTABLE): $(OBJS)
    $(CC) -o $@ $^

# Compiling source files
%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

# Clean target
clean:
    rm -f $(OBJS) $(EXECUTABLE)

.PHONY: all clean

But when I do a make clean and then run make it does appear to compile for a while but then appears to error out on alot of undefined reference errors before it exists early. Its a lot of these types of errors below, any tips appreciated!

/home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:582: undefined reference to `bip_debug_enable'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:588: undefined reference to `bip_set_port'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:596: undefined reference to `bip_get_port'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:597: undefined reference to `bip_set_port'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:602: undefined reference to `bip_set_broadcast_binding'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:606: undefined reference to `bip_get_addr_by_name'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:654: undefined reference to `bip_init'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/dlenv.c:660: undefined reference to `tsm_invokeID_set'
/usr/bin/ld: bacnet/datalink/mstp.o: in function `MSTP_Create_And_Send_Frame':
/home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:336: undefined reference to `MSTP_Send_Frame'
/usr/bin/ld: bacnet/datalink/mstp.o: in function `MSTP_Master_Node_FSM':
/home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:767: undefined reference to `MSTP_Put_Receive'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:777: undefined reference to `MSTP_Put_Receive'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:818: undefined reference to `MSTP_Get_Send'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:827: undefined reference to `MSTP_Send_Frame'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:898: undefined reference to `MSTP_Put_Receive'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:1196: undefined reference to `MSTP_Get_Reply'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:1206: undefined reference to `MSTP_Send_Frame'
/usr/bin/ld: bacnet/datalink/mstp.o: in function `MSTP_Slave_Node_FSM':
/home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:1266: undefined reference to `MSTP_Get_Reply'
/usr/bin/ld: /home/bbartling/bacnet-stack/src/bacnet/datalink/mstp.c:1278: undefined reference to `MSTP_Send_Frame'

Discussion

  • Steve Karg

    Steve Karg - 2023-12-31

    Typical usage of the library uses some defines to choose a single datalink: BACDL_BIP is usually the default, and this will then - via the apps/Makefile - choose a datalink to include in the library - apps/lib/Makefile. Either the apps/lib/libbacnet.a doesn't exist, or was not built for a datalink your code is referencing. The BACDL_BIP would also get used by datalink.h file, which then uses a macro to configure which datalink_xyz() functions are used in your build.

    Note: there is an example project of a BACnet server/client reading values from other devices. See apps/server-client.

     
  • Ben Bartling

    Ben Bartling - 2024-01-03

    Hi Steve,

    When you send out a who_is can you print/debug the response? IE.,

    Send_WhoIs(
                Target_Device_Object_Instance, Target_Device_Object_Instance);
        }
    

    I am working with a function like this below for learning purposes... where in Wireshark I can see the request was successful which confirms its working, and I was just curious to see if there is an easy way to print the MAC address or processed contents of the i_am request from the device? Or would I need create my own handler for this and loop through the contents?

    static float read_bacnet_property(uint32_t Target_Device_Object_Instance,
        BACNET_OBJECT_TYPE Target_Object_Type,
        uint32_t Target_Object_Instance,
        BACNET_PROPERTY_ID Target_Object_Property)
    {
        /* Move all variable declarations to the beginning of the block */
        BACNET_ADDRESS src = { 0 }, dest = { 0 }, Target_Address = { 0 };
        BACNET_MAC_ADDRESS mac = { 0 }, adr = { 0 };
        uint16_t pdu_len = 0;
        unsigned timeout = 100; /* milliseconds */
        unsigned max_apdu = 0;
        time_t elapsed_seconds = 0, last_seconds = 0, current_seconds = 0,
               timeout_seconds = 0;
        bool found = false;
        bool specific_address = false;
        long dnet = -1;
    
        /* Additional initializations and configurations */
        Device_Set_Object_Instance_Number(BACNET_MAX_INSTANCE);
        Init_Service_Handlers();
        dlenv_init();
    
        /* Configure the timeout values */
        last_seconds = time(NULL);
        timeout_seconds = (apdu_timeout() / 1000) * apdu_retries();
    
        printf("Initializing address...\n");
        address_init();
    
        if (specific_address) {
            printf("Configuring specific address...\n");
            bacnet_address_init(&dest, &mac, dnet, &adr);
    
            /* Debug information about address components */
            printf("MAC Length: %d, ADR Length: %d, DNET: %ld\n", mac.len, adr.len,
                dnet);
    
            if (adr.len && mac.len) {
                memcpy(&dest.mac[0], &mac.adr[0], mac.len);
                dest.mac_len = mac.len;
                memcpy(&dest.adr[0], &adr.adr[0], adr.len);
                dest.len = adr.len;
                dest.net = (dnet >= 0 && dnet <= BACNET_BROADCAST_NETWORK)
                    ? dnet
                    : BACNET_BROADCAST_NETWORK;
            } else if (mac.len) {
                memcpy(&dest.mac[0], &mac.adr[0], mac.len);
                dest.mac_len = mac.len;
                dest.len = 0;
                dest.net =
                    (dnet >= 0 && dnet <= BACNET_BROADCAST_NETWORK) ? dnet : 0;
            } else {
                dest.net = (dnet >= 0 && dnet <= BACNET_BROADCAST_NETWORK)
                    ? dnet
                    : BACNET_BROADCAST_NETWORK;
                dest.mac_len = 0;
                dest.len = 0;
            }
    
            printf(
                "Address configured. Network: %u, MAC Length: %u, ADR Length: %u\n",
                dest.net, dest.mac_len, dest.len);
            address_add(Target_Device_Object_Instance, MAX_APDU, &dest);
        } else {
            printf("No specific address to configure.\n");
        }
    
        /* Try to bind with the device */
        found = address_bind_request(
            Target_Device_Object_Instance, &max_apdu, &Target_Address);
        if (!found) {
            printf(
                "Device not found in address table. Sending Who-Is request...\n");
            Send_WhoIs(
                Target_Device_Object_Instance, Target_Device_Object_Instance);
        } else {
            printf("Device found in address table.\n");
        }
    
        return 0.0f;
    }
    
     

    Last edit: Ben Bartling 2024-01-03
    • Steve Karg

      Steve Karg - 2024-01-03

      The simplest method is to use a custom handler. The apps/whois/main.c does that with my_i_am_handler() function. The custom handler is configured by setting the unconfirmed handler:

         /* handle the reply (request) coming back */
          apdu_set_unconfirmed_handler(SERVICE_UNCONFIRMED_I_AM, my_i_am_handler);
      
       
  • Ben Bartling

    Ben Bartling - 2024-01-04

    Hi Steve,

    On a My_Read_Property_Ack_Handler is it possible to return the processed/formatted data Vs just print it?

    static void My_Read_Property_Ack_Handler(uint8_t *service_request,
        uint16_t service_len,
        BACNET_ADDRESS *src,
        BACNET_CONFIRMED_SERVICE_ACK_DATA *service_data)
    {
        int len = 0;
        BACNET_READ_PROPERTY_DATA data;
    
        if (address_match(&Target_Address, src) &&
            (service_data->invoke_id == Request_Invoke_ID)) {
            len =
                rp_ack_decode_service_request(service_request, service_len, &data);
            if (len < 0) {
                printf("<decode failed!>\n");
            } else {
                printf(" rp_ack_print_data hit\n");
                rp_ack_print_data(&data);
            }
        }
    }
    

    Thanks!
    Ben

     
    • Steve Karg

      Steve Karg - 2024-01-04

      As I mentioned earlier: there is an example project of a BACnet server/client reading values from other devices. See apps/server-client/main.c module. The application uses a helper task and state machine in src/bacnet/basic/client/bac-rw.c module to perform a ReadProperty or WriteProperty to a remote device. The module includes a callback function which includes all the parameters of the ReadPropertyACK when the data is received, and there are also hooks for errors or timeouts.

       
  • Ben Bartling

    Ben Bartling - 2024-01-06

    Hi @skarg,

    Thanks for all the info and tips so far... when I use the bacnet-stack/apps/whois $ ./bacwi 201201 for a device on my test bench it returns:

    ;Device   MAC (hex)            SNET  SADR (hex)           APDU
    ;-------- -------------------- ----- -------------------- ----
      201201  C0:A8:00:BE:BA:C0    12345 02                   286  
    ;
    ; Total Devices: 1
    

    Would this MAC in hex be what would be required when using the apps/readprop when using the arg -dnet to perform a read request using a the MAC address of the device?

    Any chance could you give me a tip on how to perform a read prop request with using the MAC address and not using the BACnet instance ID? For example I have been using the app like this:
    $ ./bacrp 201201 analog-input 2 present-value

    Thank you!
    Ben

     
    • Steve Karg

      Steve Karg - 2024-01-07

      There are two methods to bind without specifically using the Device ID:

      The demo apps compile the src/bacnet/basic/binding/address.c module to include the initialization from a file named "address_cache" which is formatted as the output of "bacwi" Who-Is application. This imports the data into the address.c address binding table and when the demo app looks up the device it returns the address listed. For example:

      $ ./bacwi > address_cache
      $  ./bacrp 201201 analog-input 2 present-value
      

      The ReadProperty app also allows 3 additional arguments: [--dnet][--dadr][--mac] which correspond to the values returned by Who-Is:

      --dnet = SNET
      --daddr = SADR
      --mac = MAC
      

      The ReadProperty app include more info about each parameter in the detailed `--help'

       
  • Anonymous

    Anonymous - 2024-01-07
    Post awaiting moderation.
  • Ben Bartling

    Ben Bartling - 2024-01-26

    Hi Steve,

    Could I ever get a tip for how to pass in a --mac address arg to the readprop app?

    In wireshark my 201201 device is this on an Iam response can a person get the MAC address from this?

    Frame 1: 66 bytes on wire (528 bits), 66 bytes captured (528 bits)
    Ethernet II, Src: Contemporary_00:d6:c2 (00:50:db:00:d6:c2), Dst: Broadcast (ff:ff:ff:ff:ff:ff)
    Internet Protocol Version 4, Src: 192.168.0.190, Dst: 192.168.0.255
    User Datagram Protocol, Src Port: 47808, Dst Port: 47808
    BACnet Virtual Link Control
    Building Automation and Control Network NPDU
        Version: 0x01 (ASHRAE 135-1995)
        Control: 0x08, Source specifier
        Source Network Address: 12345
        Source MAC Layer Address Length: 1
        SADR: 2
    Building Automation and Control Network APDU
        0001 .... = APDU Type: Unconfirmed-REQ (1)
        Unconfirmed Service Choice: i-Am (0)
        ObjectIdentifier: device, 201201
            Application Tag: BACnetObjectIdentifier, Length/Value/Type: 4
                .... 0... = Tag Class: Application Tag
                1100 .... = Application Tag Number: BACnetObjectIdentifier (12)
                Length Value Type: 4
            0000 0010 00.. .... .... .... .... .... = Object Type: device (8)
            .... .... ..00 0011 0001 0001 1111 0001 = Instance Number: 201201
        Maximum ADPU Length Accepted: (Unsigned) 286
            Application Tag: Unsigned Integer, Length/Value/Type: 2
                .... 0... = Tag Class: Application Tag
                0010 .... = Application Tag Number: Unsigned Integer (2)
                Length Value Type: 2
        Segmentation Supported:  no-segmentation (3)
            Application Tag: Enumerated, Length/Value/Type: 1
                .... 0... = Tag Class: Application Tag
                1001 .... = Application Tag Number: Enumerated (9)
                Length Value Type: 1
        Vendor ID: TAC (11)
            Application Tag: Unsigned Integer, Length/Value/Type: 1
                .... 0... = Tag Class: Application Tag
                0010 .... = Application Tag Number: Unsigned Integer (2)
                Length Value Type: 1
            Vendor Identifier: TAC (11)
    

    Thanks much!

     
  • Ben Bartling

    Ben Bartling - 2024-01-26

    Is it the 00:50:db:00:d6:c2 inside the Ethernet II, Src: Contemporary_00:d6:c2 (00:50:db:00:d6:c2), Dst: Broadcast (ff:ff:ff:ff:ff:ff) that represents the source address or src?

     
    • Steve Karg

      Steve Karg - 2024-01-27

      The address inside the Ethernet II frame is the Ethernet MAC address, which is resolved to IPv4 addresses using ARP (address resolution protocol). You can usually list the binding table of ARP using arp -a from the command line.

      For BACnet/IP, the IP address and UDP port number are used for addressing, not the Ethernet MAC address.

       
  • Steve Karg

    Steve Karg - 2024-01-27

    When using --mac option, the tools still need a device-id for pseudo-binding. Note that 4194303 is a wildcard number for the device object in newer protocol revisions:

    $ ./bin/bacrp --mac C0:A8:01:2B:BA:C0 43 8 4194303 75
    (device, 43)
    $ ./bin/bacrp --mac 192.168.1.43:47808 43 8 4194303 75
    (device, 43)
    

    If you want the device downstream of the router, you'll need to use --dnet and --dadr

    $ ./bin/bacrp --mac 192.168.1.45:47808 --dnet 2001 --dadr 2 103 8 4194303 75(device, 103)
    $ ./bin/bacrp --mac C0:A8:01:2D:BA:C0 --dnet 2001 --dadr 2 103 8 4194303 75 (device, 103)
    $ ./bin/bacrpm --mac C0:A8:01:2D:BA:C0 --dnet 2001 --dadr 2 103 8 4194303 75    device #103
    {
        object-identifier: (device, 103)
    }
    
     

Anonymous
Anonymous

Add attachments
Cancel