Playing Sound with Java223 in OpenHAB using Class.forName() and FrameworkUtil.getBundle()

Here is demonstrated by two examples how to utilize the Java223 automation add-on of openHAB, without the convenience/helper library, to dynamically utilize a class and to obtain and free an OSGi service.

The Java223 automation add-on of openHAB allows users to use the Java language to write automations. As openHAB is itself written in Java, this automation add-on has the lowest overhead compared to all other JSR223 add-ons. It allows seamless running openHAB on a 32bit ARM system with 1GB RAM. Maybe the JRule automation is the only other alternative with low overhead. DSL Rules and DSL Scripts — another JSR223 add-on — are interpreted each time they are executed, they are not compiled to Java bytecode.

The method executeCommandLine uses dynamic class resolution, enabled by OSGi’s DynamicImport-Package: *. Class.forName() must be used, because in Java223 Import-Package bundle does not contain org.openhab.core.io.net.exec. The example deliberately avoids using org.openhab.core.model.script.actions.Exec.executeCommandLine, which would eliminate the need for Class.forName(), so that eventually the org.openhab.core.model.script bundle can be disabled (stopped, not loaded, not activated) in openHAB. Disabling a bundle is done on the Karaf console with bundle:stop org.openhab.core.model.script, and as of this writing this particular bundle cannot be disabled.

The method playFile sends an .mp3 file over the webaudio and enhancedjavasound audio sinks and as multicast using ffmpeg. It allows playing pre-recorded announcements by all means - in a browser (webaudio), on the speakers connected to the openHAB server (enhancedjavasound) and by listening for public announcements. Listening is performed by some hardware, by running vlc rtp://@239.239.239.239:4444 or ffplay -nodisp rtp://239.239.239.239:4444. The file passed as parameter must be in the $OPENHAB_CONF/sounds/ directory. The playFile method obtains (gets) an OSGi service and then releases (ungets) it.

The article How to Control Smart Switches for an Off-Grid System to get the Maximum Energy from a PV inverter? contains one more example with the publishMQTT method: how to invoke actions, exposed by a Thing (binding).

import java.lang.reflect.*;
import org.openhab.core.audio.AudioManager;
import org.osgi.framework.*;

public class Sound {
  /**
   * Executes a command with parameters by invoking org.openhab.core.io.net.exec.ExecUtil.executeCommandLine(String...)
   * @see <a href="https://www.openhab.org/javadoc/latest/org/openhab/core/io/net/exec/executil#executeCommandLine(java.lang.String...)">org.openhab.core.io.net.exec.ExecUtil.executeCommandLine()</a>
   */
  static void executeCommandLine(String... commandLine) {
    try {
      Method executeCommandLine = Class.forName("org.openhab.core.io.net.exec.ExecUtil").getMethod("executeCommandLine", String[].class);
      executeCommandLine.invoke(null, new Object[]{commandLine});
    } catch (ClassNotFoundException|NoSuchMethodException|IllegalAccessException|InvocationTargetException e) {}
  }

  /**
   * Streams a file in the webaudio and enhancedjavasound sinks and as multicast.
   * @param s An {@code .mp3} file in the {@code openhab/sounds} directory without the file extension
   */
  static void playFile(String s) {
    BundleContext bcAudio = FrameworkUtil.getBundle(AudioManager.class) instanceof Bundle bundle ? bundle.getBundleContext() : null;
    if (bcAudio != null) {
      ServiceReference<AudioManager> audioManagerRef = bcAudio.getServiceReference(AudioManager.class);
      if (audioManagerRef != null) {
        AudioManager audioManager = bcAudio.getService(audioManagerRef);
        if (audioManager != null)
          try {
            audioManager.playFile(s + ".mp3", "webaudio");
            audioManager.playFile(s + ".mp3", "enhancedjavasound");
          } catch (org.openhab.core.audio.AudioException e) {}
        bcAudio.ungetService(audioManagerRef);
      }
    }
    executeCommandLine("/usr/bin/ffmpeg", "-re", "-i", "/etc/openhab/sounds/" + s + ".mp3",
                       "-filter_complex", "aresample=8000,asetnsamples=n=160", "-acodec",
                       "pcm_mulaw", "-ac", "1", "-f", "rtp", "udp://239.239.239.239:4444");
  }
}