Call light voice annunciation cycling

JHKawa

Member
Join Date
Apr 2024
Location
ne
Posts
6
Hello everyone.
I am working on designing a call light/alarm system
I have roughly 20 stations that will each have their own call switch. Then when they turn their switch on, it will tell the plc to output (using the output card as binary e.g. pins 1 & 3 on for "5") to a mp3 alarm that will say something like "station 5 needs help"
Now, all this would be very straight forward if only one person needs help at one time, but lets say stations 5 AND station 7 turn on their light at once, i need it to cycle between telling the mp3 player to say "station 5..." and then to say "station 7..."
I can make all of the mp3 files the same length, say 5 seconds or so, so a timer for 5 seconds would work to cycle between them, but not sure how to wrap my head around the program. Drum?
Also, ill be writing this in either CodeSys or Node-Red. Whichever achieves my goal, as i will be using an opto22 groov epic. I dont have my hands on the PLC yet, this is all theory at the moment, and i dont have any experience with CodeSys, but i am comfortable with A-B and have done a few things on node-red with a raspberry-pi, so im comfortable with that.

I know this is a bit of a weird one, but any ideas, help, or pointers would be greatly appreciated.
 
  • Data model
    • Use one bit in a 32-bit integer for each station to map with each call switch
      • station 1 call switch input maps to bit 1,
      • station 2 call switch input maps to bit 2,
      • etc.
      • If 32-bit integers are not available, use two 16-bit integers
  • Code model (i.e. on each scan cycle)
    • When the voice annunciation is not playing, increment a counter from 1 to 20, and wrap back to 1 from 20
    • if the bit value at that counter position in the 32-bit integer is 1, meaning that call switch is active,
      • write the low 5 bits of the counter to the 5 output bits for the light,
        • XIC bits32.[counter] XIC counter.0 OTE output.0
        • XIC bits32.[counter] XIC counter.1 OTE output.1
        • ...
      • and start a timer to keep the counter from incrementing while the annunciation is active
    • if the bit value at that counter position in the 32-bit integer is 0, then the code above will write 0s to all five annunciation outputs
    • This will examine through all call switches every 20 scan cycles (20ms or so) if no call switches are active,
      • and pause for the duration of the voice annunciation on any call switch that is active
 
That makes sense. Thank you! I was really bashing my head against a wall on this. Tying the counter to the cycle scan and pausing while annunciation is active was the big key I wasn't seeing..
 
Your PLC may have to do the bit twiddling differently, but here is a working example.
 

Attachments

  • Untitled.png
    Untitled.png
    104.4 KB · Views: 20
And it might be easier to use an array of integers.

I.e. memory is cheap so an integer, instead of a bit, value of 1 or 0 written to an element of an array of integers if that call switch is on or off, respectively, would have roughly the same structure and might be easier to follow.
 
I did something similar if you want I can send you the program, it was with about 15 AD PLCs one at each call station and they reported to one master that was located with an annuciator and call/phone line becuse it was also calling in the maintenance radio

My program would be a joke compared to what drbitboy would write but it worked, for the hardware I would look at the Click Plus

How far apart are the call stations?

It turned into a cool project that recorded responce time, downtime and had a online viewing/report, I can see if I can find it, its been 20ish years
 
I did something similar for a line with about 55 possible alarms but the iFix HMI could only display 1 alarm so the original programmer stopped looking after he found an alarm.

Unfortunately after the alarm for a tank low level (that was drained for maintenance and taken out of the cycle) there were 2 ignored alarms for undertemp that the operator shouldn't have run with. Almost a full days production was scrapped - gold plating line!

I started by setting a pointer N20:0 INT to 0 SLC5/05 PLC
Check for each alarm
If alarm present ADD 1 to pointer and MOV the alarm value to N20: (pointer) EDIT: Space because can't type : followed by ( or you get :(

That way I would have an array of every alarm active with a length of Pointer.

If Pointer = 0 at the end then there were no alarms, display 0
If Pointer = 1 then display N20:1 alarm value
If Pointer > 1 then cycle a 5 second timer and every /DN display the next alarm value

The original programmer desperately wanted to know how I did it because he had a few other HMI's that this needed done to, but didn't want to pay me for doing it so the boss let me password protect and deny future access but he kept asking.

You could do the same checking for station calls and cycling between them if more than one.
 
Wow! Thank you everyone. This was so much more and better than I had hoped for.

DrBitBoy, thank you for the example, that makes it so much more clear and easy to follow. I see what you are saying with using an integer array. Ill play around with both ways once I get my hands on the plc. Like I said, ill be using codesys, but it should work roughly identically.

GeniusInTraining, I would like to stick with the Opto22 Groov as far as hardware goes, there will be some other features that I didn't mention that that particular plc will provide for me (Webpage hosting, Emailing, Sending data to SQL, Polling data from AB plcs...) and frankly, I just want to get my hands on one.
The call stations are scattered throughout a welding department, I have 3 departments to build a system for. Each will have a separate plc and essentially be stand alone systems (except for the SQL reporting). The stations are roughly 20 meters apart from each other. I will be using a couple of Turck I/O blocks (TBEN-L4-16DXP) that will use ModbusTCP to talk to the PLC.
The Welding stations/robots all have compactlogix plcs in them, so I think I'm going to have to use Ethernet/IP for them. This shouldn't be too much trouble to do with Node-Red. But polling (or is the word pulling?) the data from those is more of a secondary (read: down the road) function of the system that I'm not as worried about for today.
I would love and appreciate having a look at your program. All the help and ideas the better right?

I_Automation, thank you, the 0, 1, >1 is how I was kind of kicking around in my head on how to do this. The more I thought about it though, it just seemed like my code would get huge. Maybe I wasn't on the right track, but it seemed to me like there would be a much more elegant solution, and that was what led me to asking about it on this site. I see how you you did the mov into an array, and I like that. I think that was similar to what DrBitBoy was referring to in his last post. Once I have the plc on hand, ill certainly give it a try.
 
I see how you you did the mov into an array, and I like that. I think that was similar to what DrBitBoy was referring to in his last post.
I am not sure I fully understand @I_Automation's algorithm. I think it might be an integer FIFO; if that is the case then no it is not similar to my approach. With the brief description provided, I don't see how to remove an alarm, or how to ensure an alarm (or rising edge of same) does not put multiple instances of the same alarm number on the FIFO.

This is what I was suggesting: basically doing the same thing as the ladder example, but using one element of an integer array (analogous to the B9 buffer bits in the example) for the buffered input bit states, so it is easier to use indirect addressing.
Code:
VAR
  alarms_as_ints : ARRAY[1..20] OF INT;   (* Buffer of 20 inputs, one integer value per %IBn discrete input *)
  alarm_index: INT;                       (* Which alarm to examine on this scan cycle *)
  five_second_ton: TON;                   (* Prevent alarm index from increments while annunciation is active *)
  timer_expired: BOOL;
END_VAR;

  (* Values of elements of alarms_as_ints array will be 0 or 1 *)
 
  alarms_as_ints[1] := %IB0.1;            (* I have no idea how to actually do this *)
  ...
  alarms_as_ints[20] := %IB2.4;

 
  IF (NOT five_second_ton.IN) OR timer_expired THEN    (* Increment current index if current alarm annunciation is not active *)
    alarm_index := alarm_index + 1;
  END_IF;

  IF alarm_index < 1 OR alarm index > 20 THEN          (* Ensure index is in range *)
    alarm_index := 1;
  END_IF;
 
  (* Run 5s timer to prevent current alarm index from incrementing *)
  (* while the current alarm's annunciation is active              *)
 
  five_second_ton(IN  := (alarms_as_ints[alarm_index] = 1) AND (NOT timer_expired)
                 ,PT  := t#5000ms
                 ,OUT => timer_expired);
 
  %OB0 := %OB0 AND 0xE0;               (* Clear output bits 0-4 *)
 
  IF five_second_ton.IN THEN           (* Alarm at current index is active:      *)
    %OB0 := %OB0 OR alarm_index;       (* - Set output bits 0-4 from alarm index *)
  END_IF;
END;

Caveat: that code is untested, and may not even compile.

I am pretty sure the [0/1/>1] logic will be more complex than that, but if it's easier to follow then I would still go with it.

Once I have the plc on hand, ill certainly give it a try.
Yes, this is best, try multiple approaches, and deploy the one to production that is easiest to read (which almost certainly will not be my bit-twiddling), because the most important aspect of this algorithm is that it must be easy to debug months from now, when the operators say "we're missing alarms, and something is not working right." E.g. because one of the input channels has failed but you don't yet know that is the problem, does the code's level of clarity help or hinder detecting that is the problem and which channel has failed.
 
Last edited:
GeniusInTraining, I would like to stick with the Opto22 Groov as far as hardware goes,..... and frankly, I just want to get my hands on one.
If I had one I would give it to you... I dont like them, but everyone has their favorite flavor of ice cream

I looked and I cant find the program also unless you had the software it would do you no good anyway... that said, the most important part is take it one piece at a time, my project morphed into a huge data acquisition project that was recording downtime had a SCADA running reports, a webpage so corporate could view everything, I even got a raise for it.

Good luck on your project
 
Here is another (untested) approach that hardcodes the input buffering to a single bit.
Code:
VAR
  current_alarm : BOOL;                   (* Buffer current alarm state *)
  alarm_index: INT;                       (* Which alarm to examine on this scan cycle *)
  five_second_ton: TON;                   (* Prevent alarm index from increments while annunciation is active *)
  timer_expired: BOOL;
END_VAR;


  (********************************)
  (* Increment current index if   *)
  (* current alarm annunciation   *)
  (* is not active                *)

  IF (NOT five_second_ton.IN) OR timer_expired THEN
    alarm_index := alarm_index + 1;
  END_IF;


  (********************************)
  (* Limit current index range    *)

  IF alarm_index < 1 OR alarm index > 20 THEN
    alarm_index := 1;
  END_IF;


  (********************************)
  (* Move current alarm value to local boolean *)

  CASE alarm_index OF
   1: current_alarm := %IB0.1;
   2: current_alarm := %IB0.2;
   ...
  20: current_alarm := %IB2.4;
  END_CASE;


  (********************************)
  (* Run 5s timer to prevent      *)
  (* current alarm index from     *)
  (* incrementing while the       *)
  (* current alarm's annunciation *)
  (* is active                    *)

  five_second_ton(IN  := (alarms_as_ints[alarm_index] = 1)
                         AND
                         (NOT timer_expired)
                 ,PT  := t#5000ms
                 ,OUT => timer_expired);


  (********************************)
  (* Clear output bits 0-4        *)

  %OB0 := %OB0 AND 0xE0;


  (********************************)
  (* If annunciation at current   *)
  (* alarm index is active, then  *)
  (* set output bits 0-4 from the *)
  (* current alarm index          *)

  IF five_second_ton.IN THEN
    %OB0 := %OB0 OR alarm_index;
  END_IF;

END;
 
This is amazing!
DrBitBoy, I cant express enough how much I appreciate all the work you are putting into my problem. Thank you! I think this gets me most of the way (if not the whole way) to my solution. I think I can see the path at least now. It will probably be another month or so before I get the hardware in my hands. But ill be sure to be back if I have any questions. Basically, I'm saying, take a break, you've gone above and beyond my wildest hopes for my little post already. :)

GeniusInTraining, what do you not like about the plcs? I am pretty committed to at least try them for this project, I don't mind managing expectations..
There will be quite an aspect of data acquisition and downtime reporting down the road with mine as well, so I myself am dreading the beast that that may turn into too, but I'm new here at my job, so I'm hoping to impress a few "higher ups" with what I can pull off.
 
GeniusInTraining, what do you not like about the plcs? I am pretty committed to at least try them for this project, I don't mind managing expectations..
There will be quite an aspect of data acquisition and downtime reporting down the road with mine as well, so I myself am dreading the beast that that may turn into too, but I'm new here at my job, so I'm hoping to impress a few "higher ups" with what I can pull off.
Yep I think it would be a great project for you, remember the higher up's like bells and whistles... I did some scrolling displays showing the department outputs for the shifts and running totals for the plant, the plant manager at the time was an electrical engineer so he backed it up, I had a 4ft marquee display as soon as you walked onto the production floor.

The issue I had with the OPTO22 was everything but this was in the late 90's early 2000s so I am sure its better than what it was then, I would give them another try.
 
Here's some LAD code I use to scroll through message displays on an HMI screen. The alarm bits are stored in an array of 16-bit integers.

(Normally in the Logix 5000 world, I would use DINTs but this project was preceded by one that used a MicroLogix 1400 and I didn't have time to re-do the HMI screens that allow editing of the stored alarm messages so this is what we ended up with).

The code in the ML1400 is actually a little simpler since I don't need to do the DIV/MOD to identify the word/bit because each type of alarm is stored in a separate data file.

You could probably adapt this pretty easily to scroll through the recordings.
 

Attachments

  • AlarmScroll.PNG
    AlarmScroll.PNG
    30.9 KB · Views: 4
  • AlarmScroll_500.PNG
    AlarmScroll_500.PNG
    37.9 KB · Views: 4
I am not sure I fully understand @I_Automation's algorithm. I think it might be an integer FIFO; if that is the case then no it is not similar to my approach. With the brief description provided, I don't see how to remove an alarm, or how to ensure an alarm (or rising edge of same) does not put multiple instances of the same alarm number on the FIFO.
No, I didn't use a FIFO.

I reset the pointer to 0 every scan then checked all the alarms and ended up with an array of all the active alarm codes (which did get entered in numerical order and the display scrolled through that order)

If an alarm went away (IE - a not latching warning that self clears) then that display would immediately drop out of the array and at the end the pointer would be one less.

This way it was one rung to 0 the pointer then one rung per condition checked. For the OP 21 lines to set the array.

The display was 1 rung if pointer=0, 1 if pointer =1 and if >1 then the timer, display pointer and display was 4 or 5 rungs
 
Last edited:

Similar Topics

I've gotten to the learning curve where I can program a call for pump to come on at set point but I'm not sure how to turn the same pump off when...
Replies
1
Views
141
Hi Siemens Experts, I am hoping someone can shed some light on my issue. I have uploaded the code from a S7-300 (317-2PN/DP) using Step 7...
Replies
9
Views
690
I created this FC and it compiles, but when I use it in my MAIN OB1, it appears to have two outputs. See pictures. What am I doing wrong here...
Replies
9
Views
1,486
Is there a way in CX-Programmer to call Ladder Instructions from a structured text program? I can see several functions in the autocomplete...
Replies
3
Views
1,990
Is there a way in CX-Programmer to call functions from a structured text program? I have found this manual that seems to describe the process...
Replies
1
Views
1,070
Back
Top Bottom