BQL Program Object

Hi,

New to the site and looking to develop a program object that will return the results of a BQL query to a numeric slot. In our case we are looking to see how many points in a device folder with alarm extensions are in alarm. We have played with the Vykon Pro BQL modules, but they are setup to return point values that equal defined values.

Found this sample code but doesn’t compile in Program Editor

Sample BQL
station:|slot:/Drivers/SnmpNetwork/Alpha_DCPlant_1/points|bql:select * from alarm:AlarmSourceExt where alarmState > ‘0’
station:|slot:/Drivers/SnmpNetwork/Alpha_DCPlant_1/points|bql:select * from control:ControlPoint where alarm.status = ‘true’

import javax.baja.sys.;
import javax.baja.naming.
;
import javax.baja.data.*;

public class MyBqlProgram extends BProgram {
public void onExecute() throws Exception {
// Define the BQL query as an ORD string using the cell schema
// This example counts all points in a station with “Fault” status
BOrd queryOrd = BOrd.make(“station:|slot:/|bql:select count(status) from control:Point where status.fault = ‘true’|cell:0,0”);

// Fetch the result from the station
Object result = queryOrd.get(Sys.getStation());

// Cast the result to a BValue (or a specific type like BDouble if expected)
// and extract the value
if (result instanceof BValue) {
  double faultCount = ((BValue) result).getDouble();
  // Use the faultCount in your program logic, e.g., setting a point's value
  setDevicesInFault(faultCount);
}

}

// Define an output point or field (e.g., a ‘devicesInFault’ NumericPoint field)
public final BDouble devicesInFault = new BDouble();
public void setDevicesInFault(double v) { setValue(“devicesInFault”, BDouble.make(v));
}
}

We have been using a developer who retired and haven’t found a replacement. Had Ecilpse with the Niagara plugin working in the AX days, but that was years ago.

Thanks

Ken

1 Like

Hi Ken, welcome!

I’ve got this working on a demo station to show you how it looks, check out the screenshots I attached. The first image shows the Program object successfully pulling a count of 2.00, and the second shows the FCU1_1 graphic where those alarms are originating.

Here is the working code I used for that Program object:

java

public void onExecute() throws Exception
{
  try {
    BOrd basePath = getQueryPath();
    if (basePath == null) {
      basePath = BOrd.make("station:|slot:/Drivers");
    }
    
    // Use |cell:0,0 to extract single value directly
    String queryStr = basePath.toString() + 
        "|bql:select count(*) from alarm:AlarmSourceExt|cell:0,0";
    
    BOrd queryOrd = BOrd.make(queryStr);
    Object result = queryOrd.get();
    
    double count = 0.0;
    if (result instanceof BInteger) {
      count = ((BInteger)result).getInt();
    } else if (result instanceof BLong) {
      count = ((BLong)result).getLong();
    } else if (result != null) {
      count = Double.parseDouble(result.toString());
    }
    
    BStatusNumeric out = getAlarmCount();
    out.setValue(count);
    
  } catch (Exception e) {
    e.printStackTrace();
  }
}

Setup:

  1. Create a Program object

  2. Add slots via the Slots tab:

    • alarmCount (Type: baja:StatusNumeric, Flags: so)

    • queryPath (Type: baja:Ord, Flags: so)

  3. Set queryPath to your folder, e.g.: station:|slot:/Drivers/SnmpNetwork/Alpha_DCPlant_1/points

  4. Trigger execution via schedule or link


Alternative Approaches (No Programming Required)

Depending on your needs, these might be simpler:

  1. AlarmService Counts - If you assign unique Alarm Classes to each device folder, the AlarmService tracks counts per class automatically. Check station:|slot:/Services/AlarmService for properties like openAlarmCount.

  2. AlarmConsole/AlarmDb - The alarm database has built-in count properties you can link to directly.

  3. Bound Labels - For display-only (not logic), a BoundLabel in a PX view can show BQL results directly without a program.

Hope this helps!

image

1 Like

This looks like a great start. Need to add the code for an interval timer and a way to pick the parent folder for the query. I am curious if you have any concerns about system resource with a bunch of these alarm summary programs running concurrently at 10-30 second intervals.

This requirement is for a multi site customer that we display the number of alarms and comm faults for each site on the dashboard. Some of the alarms come from a Jace and others are devices that are directly mapped to the Server using ModbusTCP, SNMP or BacnetIP. As this system is expanding we are pushing to use Jaces instead of the direct mapping path.

Thanks for the help with this. I have another function I have asked Tridium for help with and only heard crickets!

Thanks

Ken

1 Like

So tested the program code and changed the query to just look for alarm extensions not normal.

where alarmState != ‘normal’

This code works fine if there is an active alarm, but once the alarms are clear I get an error

java.lang.IndexOutOfBoundsException
at javax.baja.collection.Tables.get(Tables.java:83)

Not sure of the best way to handle an empty result query.

Also doing this in the Tridium compiler is very painful and miss my old Eclipse complier with the Tridium plugin.

1 Like

Hi Ken,

Good catch on the empty result issue! When count(*) returns no rows, the |cell:0,0 throws an IndexOutOfBoundsException. Here’s the fixed code with null/empty handling:

java

public void onExecute() throws Exception
{
  try {
    BOrd basePath = getQueryPath();
    if (basePath == null) {
      basePath = BOrd.make("station:|slot:/Drivers");
    }
    
    // Query for active alarms only
    String queryStr = basePath.toString() + 
        "|bql:select count(*) from alarm:AlarmSourceExt where alarmState != 'normal'|cell:0,0";
    
    BOrd queryOrd = BOrd.make(queryStr);
    
    double count = 0.0;
    
    try {
      Object result = queryOrd.get();
      
      if (result != null) {
        if (result instanceof BInteger) {
          count = ((BInteger)result).getInt();
        } else if (result instanceof BLong) {
          count = ((BLong)result).getLong();
        } else {
          count = Double.parseDouble(result.toString());
        }
      }
    } catch (IndexOutOfBoundsException e) {
      // Empty result set = 0 alarms
      count = 0.0;
    }
    
    BStatusNumeric out = getAlarmCount();
    out.setValue(count);
    
  } catch (Exception e) {
    e.printStackTrace();
  }
}

The key fix is wrapping the query execution in its own try-catch for IndexOutOfBoundsException - when no alarms exist, we just set count to 0.

Performance Concerns

For a handful of programs at 10-30 second intervals, you should be fine - BQL count queries are lightweight. However, with “a bunch” running across many sites, here are some considerations:

  1. Stagger execution times - Don’t trigger all programs at exactly :00/:30. Use slightly offset schedules (site1 at :00, site2 at :02, etc.)

  2. Consolidate into one program - Instead of 20 separate programs, one program could iterate through a list of paths and update a set of output slots. Runs once, updates all counts.

Example of that code could be something like this:

Required Slots

Name Type Flags Description
site1Path baja:Ord so Path to Site 1 folder
site1Count baja:StatusNumeric so Site 1 alarm count
site2Path baja:Ord so Path to Site 2 folder
site2Count baja:StatusNumeric so Site 2 alarm count
site3Path baja:Ord so Path to Site 3 folder
site3Count baja:StatusNumeric so Site 3 alarm count
site4Path baja:Ord so Path to Site 4 folder
site4Count baja:StatusNumeric so Site 4 alarm count
site5Path baja:Ord so Path to Site 5 folder
site5Count baja:StatusNumeric so Site 5 alarm count
totalCount baja:StatusNumeric so Total across all sites

java

public void onExecute() throws Exception
{
  try {
    double total = 0.0;
    
    // Process each site
    total += processSite(getSite1Path(), getSite1Count());
    total += processSite(getSite2Path(), getSite2Count());
    total += processSite(getSite3Path(), getSite3Count());
    total += processSite(getSite4Path(), getSite4Count());
    total += processSite(getSite5Path(), getSite5Count());
    
    // Update total
    getTotalCount().setValue(total);
    
  } catch (Exception e) {
    e.printStackTrace();
  }
}

/**
 * Process a single site - returns count (0 if path is null/empty)
 */
private double processSite(BOrd sitePath, BStatusNumeric countSlot) 
{
  double count = 0.0;
  
  try {
    // Skip if path not configured
    if (sitePath == null || sitePath.toString().isEmpty()) {
      return 0.0;
    }
    
    // Build query for active alarms
    String queryStr = sitePath.toString() + 
        "|bql:select count(*) from alarm:AlarmSourceExt where alarmState != 'normal'|cell:0,0";
    
    BOrd queryOrd = BOrd.make(queryStr);
    
    try {
      Object result = queryOrd.get();
      
      if (result != null) {
        if (result instanceof BInteger) {
          count = ((BInteger)result).getInt();
        } else if (result instanceof BLong) {
          count = ((BLong)result).getLong();
        } else {
          count = Double.parseDouble(result.toString());
        }
      }
    } catch (IndexOutOfBoundsException e) {
      // Empty result = 0 alarms
      count = 0.0;
    }
    
    // Update the output slot
    if (countSlot != null) {
      countSlot.setValue(count);
    }
    
  } catch (Exception e) {
    System.out.println("Error processing site: " + e.getMessage());
  }
  
  return count;
}

Hope that helps!

1 Like

Sounds like a typical Tridium quirk, why not return zero when asking for a count instead of throwing an error? I will give Tridium a hard time at the AHR show this week. If you are going I would love to buy you dinner/drinks for your help.

Curious why the check for all the different types? Does Tridium not return count values as an Integer or Long values? I would think this would all you need:

if (result != null) {
count = ((BInteger)result).getInt();

This program will be used on each device (Jace or Supervisor) that will connect to an Enum that provides a simple status.

0=Off, 1 =On (or Normal), 2 = Alarm, 3= Comm Fault

We then aggregate the pXStatus (our name for the enum) points by site to show on a dashboard alarms/comm faults per Site and State.

We deal with all sorts of devices, some with a common alarm and others that have dozens of system alarms along with any points we add extensions to. Typically we link the alarm status to a SuperOr which is pain, and if we add a point need to make sure it gets linked.

Last thing we need is code to pick up the parent folder just like an alarm or history ext does.

Also is there a decent reference for all the functions for things like IndexOutOfBoundsException descriptors?

Thanks again!

1 Like

Yeah, no real reason for all the type checking - just AI being overcautious lol.

As for references - the Tridium docs really help with the classes you want to extend, but when it comes to the compiler and tracking down errors it’s usually me, Google, and whatever AI going at it until I get the results I want. The standard Java documentation covers all exceptions:

Between some YouTube videos, the great @CharlesJohnson, and lots of late nights just going at it, there really hasn’t been much more than trial and error until you get what you want. Once you have the Java basics and patterns understood along with the baja docs, it does slowly start making more sense.

So in the last year or so of learning I think I’ve figured out just enough to have it all be useless come N5 I’m sure lol. But keep learning! If you need any help or want to share backups and chat about other options just let me know.

Enjoy the expo!

1 Like

AI strikes again.

So does that count return a integer or long?

What about code for the parent folder. Thinking that would go in the startup function and fire when you drop the program in the folder.

AX community has a parent name function but want this with no external links.

1 Like

It returns an integer, and if I understand what your looking for you can add the folder path so when you drop the program it fills the queryPath then you can execute and get count here:

public void onStart() throws Exception
{
  // Auto-set queryPath to parent folder on startup
  BComponent parent = (BComponent)getComponent().getParent();
  if (parent != null) {
    String parentPath = "station:|" + parent.getSlotPath().toString();
    setQueryPath(BOrd.make(parentPath));
  }
}

public void onExecute() throws Exception
{
  try {
    BOrd basePath = getQueryPath();
    
    // Safety check if queryPath not set
    if (basePath == null || basePath.toString().isEmpty()) {
      BComponent parent = (BComponent)getComponent().getParent();
      if (parent != null) {
        basePath = BOrd.make("station:|" + parent.getSlotPath().toString());
      } else {
        return; // Can't run without a valid path
      }
    }
    
    String queryStr = basePath.toString() + 
        "|bql:select count(*) from alarm:AlarmSourceExt where alarmState != 'normal'|cell:0,0";
    
    BOrd queryOrd = BOrd.make(queryStr);
    int count = 0;
    
    try {
      Object result = queryOrd.get();
      if (result != null) {
        count = ((BInteger)result).getInt();
      }
    } catch (IndexOutOfBoundsException e) {
      count = 0;
    }
    
    getAlarmCount().setValue(count);
    
  } catch (Exception e) {
    e.printStackTrace();
  }
}

image

1 Like

So version 1 is working but I not happy with the fact most of time there are no alarms and my queries will be empty most of the time throwing an error. I like the way the Vykon Pro BQL functions and builds a list of elements once (and refreshed at a longer interval or manually) and we count the state of the points, or in my case the number of Alarms Extensions that are in alarm.

Thanks

I was hoping this thread was still active to come up with a better way to query for points in alarm. My code is working but seems that running frequent queries that are null causing an exception is not the best way to do this.

Using the VykonPro bql modules as an example, There must be a way to build a query of all the alarm extensions in a folder that you can just check to see which are not in alarm:

query1= station:|slot:/Drivers/AlarmTest|bql:select * from alarm:AlarmSourceExt

BOrd queryOrd1 = BOrd.make(query1);

This query would only execute if you added and deleted points with alarm extension. and instead of this code that returns an exception when everything is nomal:

// Use |cell:0,0 to extract single value directly
String queryStr = basePath.toString() + 
    "|bql:select count(*) from alarm:AlarmSourceExt where alarmState != 'normal'|cell:0,0";
// Return count of active alarms
    BOrd queryOrd = BOrd.make(queryStr);
    Object result = queryOrd.get();

To something that would work on the saved query of alarm extensions  

    "|bql:select count(*) from queryOrd1 where alarmState != 'normal'|cell:0,0" 

Not sure how to save a query handle and run another query against it.

Hope this makes sense.