Multistate triggers

Triggers in TeSSH can have more than one state. This feature allows you to chain together different events and can be very powerful. You can add new trigger states to a trigger with the #cond command or with the New State option in the Package Editor. The important things to remember when writing multistate triggers are that:

  • Only one state is active for each trigger at any given time.
  • Trigger states have a sequential order (by default). When one state fires, the next state becomes active.
  • Some trigger types are only usable in a sub-state of a trigger (a substate being any state except the first).

The first point above is the most important: Only one state per trigger is active at any time. The other states aren't checked while they're not active.

Each state has its own properties. Options like case sensitivity, regular expression and so on can all be set for each state individually. Simply select the substate from the treeview and you'll be able to edit its options. The only exception is the ID field - the ID of the parent trigger is always used, regardless of which state is currently active.

Trigger types can also be mixed and matched amongst the various states of a trigger - the only rule is that the substate-only types cannot be used for a parent trigger. The following are detailed examples for each trigger type:

Sequential Patterns

This is the simplest kind of multistate trigger. Let's look at a simple example of a trigger with two states, both of which are Pattern states. The first pattern will be "Status:", the second pattern "Connection":

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue}

The #COND command adds a new state to the previously defined trigger. So now, what happens if the server sends the text "Connection"? Nothing! Remember, only one state is active at a time, and since we just defined this trigger, the first state is active, so it's looking for the text "Status:". This trigger isn't going to do anything until it receives the "Status:" text first. Once the server sends "Status;", then the first trigger state fires, and colors that text bright red. Then it advances to the next state within the trigger, which is the Connection state. Now the trigger is waiting for the text "Connection". If the server sends "Status:" again, it is ignored. Only when the server sends "Connection" does the trigger fire, coloring the text bright blue, then advancing to the next state.

Since we are at the last state, the trigger wraps around and the first state becomes enabled again.

So, what did we just create? A trigger that first waits for the text "Status:", then waits for the text "Connection". It doesn't matter how much text the server sends between these two patterns. We could emulate this behavior without using a multistate trigger by using the following code:

#TRIGGER {Status:} {#T- FirstTrigger;#CW high,red;#T+ SecondTrigger} "FirstTrigger"
#TRIGGER {Connection} {#T- SecondTrigger;#CW high,blue;#T+ FirstTrigger} "SecondTrigger"

The above example uses two triggers in different trigger classes. When the first trigger fires, it disables itself and activates the second trigger. When the second trigger fires, it disables itself and activates the first trigger. But this is more complicated than using the new trigger states since it requires 2 separate triggers and 2 separate class folders, cluttering up your script settings. And it only gets more complicated the more triggers you try to add, but adding states is easy.

Skip Lines

OK, we will start this example with a normal Pattern trigger. Then follow it with a Skip state. The trigger will remain dormant until the initial pattern is received. When this initial pattern is received, the trigger moves to the next state, which is the Skip step. The Skip step will ignore the next 5 lines from the server, then fire on the next line that matches the second pattern.

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue} {Skip|Param=5}

Notice the Options field, where we specify a string list of options, giving the name of the trigger type, followed by the setting for the Trigger Parameter (Param) option. The Param option is used to give numeric parameters to the new trigger states. In this case, Param is the number of lines we want to skip. In the GUI, when you select a trigger type from the drop-down box at the bottom, a field will appear for you to enter your parameter.

Now, with this trigger entered and active, we get a line from the server that says: "Connection". Nothing happens yet. The trigger is still waiting for the initial pattern "Status:" to be received from the server. OK, so now the server sends the line "Status:". The initial trigger fires, and colors this text bright red. The trigger advances to the next state, so now the Skip pattern is activated. The parameter we gave to skip was 5, so the next 5 lines are ignored. If the server sends "Connection", nothing happens - remember, the original pattern is no longer active.

Next, the server sends "Connection". Again, it is ignored because that was only the second line. The server now sends line 3, 4, and 5. Doesn't matter what text are on these lines, since the trigger is skipping them. Now we've had 5 lines from the server and ignored them. So now, as soon as the server sends the "Connection" pattern, the Skip state will fire and color the text bright blue. If the server sends different text before it sends "Connection" (like "Status:" again) it is ignored. The trigger is waiting for the "Connection" text. Once the server finally sends "Connection", it is colored bright blue, and the trigger advances to the next state.

Since we were already at the last state, the trigger wraps around back to the first state. So now the original "Status:" state is active again.

There is no easy way to emulate this behavior without using a multistate trigger. You'd need a very complex script that could count lines from the server. It would have looked something like this:

#TRIGGER {Status:} {#T- FirstTrigger;#CW high,red;#VAR LineCount 0;#T+ SecondTrigger} "FirstTrigger"
#TRIGGER {(%*)} {#ADD LineCount 1;#IF (@LineCount >= 5) {#T- SecondTrigger;#T+ ThirdTrigger}} "SecondTrigger"
#TRIGGER {Connection} {#T- ThirdTrigger;#CW high,blue;#T+ FirstTrigger} "ThirdTrigger"

What a mess! And imagine if you had four or five Skip states in the same trigger!


For the "Wait" trigger type, we will look at two examples. First, an example similar to the last one:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue} {Wait|Param=5000}

In this case, Param is the number of milliseconds the trigger should wait before starting to match. So the server must first send the text "Status:" to fire the first pattern. Now we are on the second pattern and waiting for 5000 ms, or 5 seconds. Any text received from the server during this 5 seconds is ignored. Once the 5 seconds is up, the trigger waits to receive the "Connection" text. When it does, it is colored in bright blue, and we advance to the next trigger state, which wraps back to the first state.

The above example could be emulated without multistate triggers like this:

#TRIGGER {Status:} {#T- FirstTrigger;#CW high,red;#ALARM +5 {#TEMP {Connection} {#T+ FirstTrigger;#CW high,blue}}} "FirstTrigger"


OK, for the second example, what happens when there is no text pattern for the Wait state? Here is the example:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {} {#BEEP} {Wait|Param=5000}

Well, first we wait for the text "Status:" from the server to arrive. Then the trigger colors the text in bright red, and advances to the Wait part of the trigger. The trigger waits for 5 seconds. When it's done, it notices that the pattern is empty, which causes the trigger to fire immediately, sounding the #BEEP.

The difference between using a Wait state in a trigger and simply using the #WAIT command is that the trigger state does not create any new threads, so they can be used without fear of causing thread-related problems.

Loop Pattern

The "Loop Pattern" type fires a trigger a specific number of times when the pattern matches the text from the server. Here's an example:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue} {LoopPat|Param=5}

Once again, the trigger first waits for the text "Status:" to come from the server. Then it colors the text in bright red and moves to the next state. In this case the second state of the trigger is looking for the text "Connection" from the server. Any other text from the server is ignored. The next five times the server sends "Connection", the text will be colored bright blue. When the fifth "Connection" is received from the server, the text is colored blue, but then the trigger state is done, and it increments to the next state, wrapping around to the first. Now "Connection" will be ignored again until "Status:" is received from the server to fire the trigger again.

Loop Lines

The "Loop Lines" type will fire the trigger whenever the pattern is matched during the next N lines. Once N lines have been received, the trigger state is automatically incremented. For example:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue} {LoopLines|Param=5}

Once the "Status:" text is received from the server, the trigger will then look at the next 5 lines from the server. If any of those lines contains the "Connection" text, it will get colored bright blue. Once the 5 lines have been received, it increments to the next trigger state, and wraps back to the beginning.

So, what happens if the LoopLines pattern is empty? Well, then it will match any text on the next 5 lines. So, for example:

#TRIGGER {Status:} {#VAR Status ""}
#CONDITION {} {#ADDITEM Status %line} {LoopLines|Param=5}

This trigger would wait until the server sent the word "Status:". It would then clear out the @Status variable and then take the next 5 lines from the server, putting each line into the @Status variable as a new item in a string list.

Resetting Triggers

What if you wanted to end the previous trigger before the 5 lines were up? For example, what if you wanted to collect the status until a blank line was received? Well, you can use the #STATE command to set the current state of any trigger. Setting a trigger state back to zero resets the trigger. So, you could do the following:

#TRIGGER "StatusTrig" {Status:} {#VAR Status "";#TEMP {^$} {#STATE StatusTrig 0}}
#CONDITION {} {#ADDITEM Status %line} {LoopLines|Param=99}

OK, so there are a couple of new items to explain in the previous example. First, notice that we have given the trigger a name of "StatusTrig". This is the ID of the trigger and can be used by many other commands to control the trigger. For example, you can use the trigger ID in a #T- command to disable a specific trigger. In this case, we use the StatusTrig name in the #STATE command to reset the trigger back to state 0. States are numbered starting from 0, so state 0 is the first state, the "Status:" state. This is done with a Temporary trigger that waits until a blank line is received and then resets the state. The Param=99 gives a limit on the number of lines we will add to the inventory.

Remember that only one trigger state is active at a time. So, in order to watch the server for a blank line, we needed to create a second trigger. While the first trigger is adding lines to the @Status variable, the second temporary trigger is waiting for a blank line to reset the first one. The #TEMP command is a good trick for creating a trigger that you just want to fire once. Notice that we didn't have to use any trigger classes in this example.

Loop Expression

The "Loop Expression" state will fire the trigger for each line that is received while the Expression given in the Pattern field is true. Normal Expression triggers only execute when the expression is true when any variable is set. The Loop Expression type is a way to continue performing an action while an expression is true. However, keep in mind that with the Loop Expression type, the expression is only tested when a new line is received from the server.

Consider this difference:

#TRIGGER (@a=1) {It matched!}

As soon as the variable @a becomes 1 (for example, enter "A=1" on the command line), the trigger will fire. It doesn't need any text from the server to execute. Now try a new Loop Expression trigger:

#TRIGGER (@a=1) {Hello!} "" "LoopExp"

Enter "A=1" on the command line. Notice this trigger doesn't fire yet. But now, for each line received from the server, it will fire. Set A=0 and now it will stop firing.

The Param for LoopExp gives the maximum number of times the trigger will fire. So, for example:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {@a=1} {#BEEP} {LoopExp|Param=2}

This will wait for the "Status:" text from the server. Then, if @a=1 it will beep for the next 2 lines, or until @a stops being 1, whichever happens first. If @a isn't equal to 1 when the first state matches, the trigger will reset back to the first state.


The "Duration" state will fire the trigger for each line that is received which matches the pattern within the given number of milliseconds. After the given number of milliseconds have expired, it will automatically increment to the next trigger state. Note that unlike the Wait type, a line of text must be received from the server in order to test the clock. The trigger will not increment to the next state unless a line is received from the server. For example:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue} {Dur|Param=5000}

First the trigger waits to receive "Status:" from the server. Then, for the next 5 seconds, any line that contains the word "Connection" is colored in bright blue. If the pattern is empty, than any line from the server received in the time interval will cause the trigger to fire.

Within Lines

The "Within Lines" type will fire the trigger if the pattern is received within the next N lines. For example:

#TRIGGER {Status:} {#CW high,red}
#CONDITION {Connection} {#CW high,blue} {Within|Param=5}

This will wait for the "Status:" text from the server. Then, if "Connection" is received within the next 5 lines of text, it will be colored bright blue. If 5 lines are received without seeing the "Connection" text, the trigger resets (it does not advance to the next state).

This is one way to handle a multi-line trigger. For example:

#TRIGGER {Pattern1} {}
#CONDITION {Pattern2} {command} {Within|Param=1}

This would only execute the "command" if the Pattern2 was on the line immediately following Pattern1. You could emulate this without using multistate triggers by doing:

#TRIGGER {Pattern1$Pattern2} {command}

Seems simpler, but this kind of multi-line trigger is much slower to process than the Within state syntax. Also, the Within syntax allows you to match more than just two lines very easily. For example:

#TRIGGER {Pattern1} {}
#CONDITION {Pattern2} {} {Within|Param=1}
#CONDITION {Pattern3} {command} {Within|Param=1}


Sometimes you might want to match a trigger on the same line that has already been tested. For example, you might want to split up multiple tests of the same line into different states.

When a state has a type of ReParse, the same line that matched the previous trigger state is tested again against the Pattern of the trigger state. If the pattern matches, then the commands for the state are executed. However, unlike other states, TeSSH increments to the next trigger state in the list, no matter whether the ReParse state matched or not.

For example:

#TRIGGER {Status:} {}
#CONDITION {Error} {#ADD Error 1} "reparse"
#CONDITION {Warning} {#ADD Warning 1} "reparse"

This trigger waits until "Status:" is received from the server. When this line is received, the first state matches, and since there are no commands, it just increments to the next state. The next state is a ReParse state, so it checks the same "Status:" line and if the word "Error" is anywhere in the line, it increments the @Error variable. Then, regardless of whether this state matched, TeSSH goes to the next state. The next state is another ReParse state which now checks the same "Status:" line for the word "Warning". If the word "Warning" appears anywhere in the line, the @Warning variable is incremented. Then, regardless of whether "Warning" appeared in the line or not, the trigger state is incremented, looping back to the beginning of the trigger, where is waits for another line from the server.

You could create this example without using trigger states with the trigger:

#TRIGGER {Status:} {#IF (%trigger =~ "Error") {#ADD Error 1};#IF (%trigger =~ "Warning") {#ADD Warning 1}}

but the ReParse trigger type is easier, faster and more flexible than this.

Add comment

Login or register to post comments