Sound for critical alarms

Hello,

Quick question in regards to the Audio component in Perspective.

I wish to have a sound play when a critical alarm is triggered.

What would be the best way to achieve this?

Anyone have any experience with this?

You cannot rely on the browser your Perspective client is using to allow auto-play of your sound, nor be sure the sound on that PC is even on. You cannot even rely on there being an open Perspective client to play the sound.

Critical audible alerts must be wired to the system, not to any client. Preferably directly to the system's PLC, but possibly to the Ignition gateway (where a gateway event can use system.util.playSoundClip() to play through the gateway's sound card).

3 Likes

If Ignition handles defining alarm priority, what is considered best practice for the alerts to be wired to a PLC? I've always wanted to ask others what they do on this. I know I could hard code in the PLC, but then that opens up discrepancies between what the HMI shows as an alarm priority and the PLC.

I usually setup an alarm listener and then send this data to the PLC by a gateway event. The alarm listener opens up the possibility to handle different priorities or even other alarm properties.

If a PLC is going to sound or flash the alarm (literally), I don't make the logic rely on the gateway. That's the point of having the PLC do it.

1 Like

Right, so how do you correlate the two (HMI <--> PLC)? We usually wire horns/lights to the PLC simply because that's easier to do, not because it's a "critical" function. Just curious on what others do on this, not meaning to hijack a thread or get in a needless debate.

When there is an HMI independent of Ignition (I prefer Vision on machines...), I don't see how the priority coordination can be anything other than custom. :man_shrugging:

When Ignition is also the HMI, I prefer that the PLC simply generate the alarms, with Ignition following the PLC's lead for critical alarms, and Ignition setting the priority for anything else. And Ignition handling all acknowledgements, if any.

1 Like

Ok, custom is all I could think of as well.

Thanks for the feedback!

Here's how i solved it.

Views/AlarmView

  1. Created an Audio Component
  2. Created a Custom Property on the View
  • Name: playAlarm
  • Type: Boolean (automatically set to False upon creation).

image

  1. Expression Binding on playAlarm
    I set up an Expression Binding using runScript() to check for active alarms:
runScript("len(system.alarm.queryStatus(priority=['Critical'], state=['ActiveUnacked'])) > 0", 2500)
  • This checks every 2.5 seconds if there are any alarms with the following criteria:
    • Priority: Critical
    • State: ActiveUnacked (Active and Unacknowledged).
  • The result (True/False) updates the playAlarm property.
  1. Binding playAlarm to Audio Component's props.play
  • I linked the play property of the Audio Component directly to the playAlarm property.

  1. Audio Component's props.source
  • Added a test sound with the URL:
https://www2.cs.uic.edu/~i101/SoundFiles/taunt.wav

  1. Test Tag
  • Created a test tag with a Critical setpoint to verify the alarm functionality.

Behavior

  • This configuration will allow the alarm sound to play only on this specific view (Views/AlarmView).

Global Playback Configuration

To enable sound playback globally, on any view or through a main view, i:

  1. Used Session Properties for the playAlarm value.
  2. Placed the Audio Component on a persistent view, /headerview in this case.

Testing

  • I tested the alarm by setting the value of the test tag to 100 (Critical setpoint), and the sound triggered successfully in the client.

You really should not be running that expression binding on a view like that. You're going to cause yourself a lot of headaches if/when you have lots of clients connected all running that query every 2.5 seconds.

Instead, run a timer script on the gateway that runs your script and just writes the either the count of alarms or a boolean value to a tag. Then you can use your binding off of that tag instead of a runScript expression. This guarantees it to be up to date and only running once every 2.5 seconds no matter how many clients you have.

4 Likes

Thank you so much, man.

I'm going to revise this and update the solution accordingly.

Current solution:

Any feedback is greatly appreciated :slight_smile:

This still doesn't solve all problems, specifically in regards to issues pturmel mentioned. But hopefully this is somewhat more efficient.

Gateway timer script

I created a gateway timer script to monitor active critical alarms and update a memory tag named CriticalAlarmCount.

results = system.alarm.queryStatus(priority=["Critical"], state=["ActiveUnacked"])
critical_alarms = len(results)
system.tag.writeBlocking(["[TagProvider]TagFolder/CriticalAlarmCount"], [critical_alarms])

Session property

I added a custom session property named playAlarm with this expression binding:

try({[TagProvider]TagFolder/CriticalAlarmCount} > 0, false)

..triggering True when any critical alarm is active.

View Property change script

On the View where the Audio Component is located, I created a property change script for playAlarm.

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
    if currentValue.value == True:
        self.getChild("root").getChild("Audio").props.play = True

...sets Audio.props.play to True when playAlarm becomes True.

Audio component

  • Source: URL of the audio file.
  • Play: Controlled via the playAlarm logic.

If i were to use the gateway and system.util.playSoundClip for this - does anyone have any resources or tips for how to set that up?

Using system.util.playSoundClip("/sound/alarm.wav", 1.0, False) to trigger sound from GW (file is mounted to docker volume)

Getting an error that the file format is not supported by javax.sound.sampled API requirments

As far as i can see from the error, the requirements are these:

  • Sample rate: 22050 Hz
  • Bit depth: 16-bit
  • Channels: Mono
  • Encoding: PCM_SIGNED

Error message:

Caused by: org.python.core.PyException: java.lang.IllegalArgumentException: java.lang.IllegalArgumentException: No line matching interface Clip supporting format PCM_SIGNED 44100.0 Hz, 16 bit, mono, 2 bytes/frame, little-endian, and buffers of 128020 to 128020 bytes is supported.

... 26 common frames omitted

Caused by: java.lang.IllegalArgumentException: No line matching interface Clip supporting format PCM_SIGNED 44100.0 Hz, 16 bit, mono, 2 bytes/frame, little-endian, and buffers of 128020 to 128020 bytes is supported.

at java.desktop/javax.sound.sampled.AudioSystem.getLine(Unknown Source)

at com.inductiveautomation.ignition.common.script.builtin.SystemUtilities.playSoundClip(SystemUtilities.java:288)

I'm using a wav file with a bitrate of 705 kbps, which is a uncompressed 16-bit PCM WAV file

image

From what i can see, this audio file should work. Anyone have any thoughts?

Your sampling rate is too high if those requirements are strict.

1 Like

I would not expect a docker container to emulate sound hardware, and if it did, where would the sound go?

Gateway sound generation presumes gateway hardware with a sound card.

1 Like

Okay. It seems you're right. I am still new to this and was not aware of this. But thanks for pointing that out.

Consider using a VM instead of docker. Many hypervisors do provide sound card emulation that routes to the hypervisor's hardware.

1 Like