Ladder logic takes some getting used-to when you start working with it, but once you know the rules and possible pitfalls to avoid, you will find that you can accomplish almost anything using a rather minimalist instruction set.
Ocelot Ladder Logic
Ladder logic takes some getting used-to when you start working with it, but once you know the rules and possible pitfalls to avoid, you will find that you can accomplish almost anything using a rather minimalist instruction set.
If you have any questions about anything you read here, you can contact me at: firstname.lastname@example.org I only request that you do not ask me any support questions or ask for custom programming. I have no link with Applied Digital, I'm simply a happy owner of an Ocelot who wants to share his knowledge and experience with other users of this neat little controller.
I first got started in home automation about 2 years ago and like many people, my first controller was the CM11A with ActiveHome software. This package offers good basic performance at an attractive price, and helps a lot of people to get started with the concept of automating their home. This field is still quite new and evolving at a fast pace, not unlike personal computing in the early 80's ( you bought a computer…what for ?). The problem, even as recently as a year ago, was that very few controllers were available, and those that were were either quite inexpensive with basic functionality (eg. ActiveHome), or very costly but sophisticated ( eg. JDS TimeCommander units); there was no "middle ground". The inexpensive solutions are usually macro oriented, sparing you the trouble of learning a programming language and making it easy to use Windows to choose options. The more sophisticated systems, like the JDS units, allow you to create scripts to do conditional logic. This past year has finally managed to fill some of that middle ground with affordable systems that provide scripting and expandability options. One of the more popular of these new units is the Ocelot controller ( formerly called the CPU/XA ) from Applied Digital. This unit has an industrial control heritage and thus uses a programming model called "ladder logic" to control it. The JDS systems also use a higher-level form of ladder logic. This model is a natural fit for event control ( I bought the Ocelot specifically because it uses this type of logic ).
The concept of conditional logic to control hardware with delays and AND/OR conditions has been around for many years in the industrial field. I remember programming an industrial PLC (programmable logic controller ) back in 1981. These controllers are built and engineered to replace hardwired relays and switches which were the norm before that. The relay based circuits were called "ladder" circuits because the circuit diagrams for these resembled a ladder ; the two power lines were the long rails, and switches and relays looked like the rungs. I have included a sample relay diagram to illustrate this:
When programmable controllers were designed to replace the relays, engineers wanted to retain the same logic model to allow existing designs to be adapted to the new hardware. This has resulted in Ladder Logic controllers ( LL from now on ). Since most users of home automation are usually more computer literate than industrial control minded, they have learned the usual crop of languages like BASIC or C . These languages work fine because we are usually doing things one-at-a-time, like entering data, performing calculations, or accessing files. We don't even stop to think about problems like "what if I'm in the middle of entering a field on a menu and the program needs to read a file ?" because our program is sitting there, waiting for us to finish entering a value before it can do anything else. Newer languages like Visual Basic ( VB ) took some re-thinking because suddenly, the program did not have absolute control over events; the user could click on any of several buttons, even if it made no sense, like clicking on "OK" without having entered required information beforehand. Programmers found themselves doing a whole lot more error checking and enabling/disabling controls to "idiot proof" the process as much as possible. LL is a bit like VB, needing a lot of idiot proofing of its own. What does all this bring you ? In a word: multitasking. We are already used to multitasking operating systems, running several programs at once. Here, we are talking of a multitasking program.
LABEL TopOfLoop INPUT X10receive$ . . IF X10receive$ = "A1-ON" WAIT 3 X10send$="B1-ON" OUTPUT X10send$ ENDIF . . GOTO TopOfLoop
Ocelot STATEMENT COMMENT IF X10 A/1 ON Command If an X-10 A-1 ON was received THEN Timer0=1 start timer 0 by setting to 1 . . IF Timer0 Becomes > 3 on the first time timer 0 reaches 4 THEN Timer0=0 stop the timer THEN X10 Quick On B/1 and send an X-10 B-1 ON command . . END OF PROGRAM
As you can see, they look similar. The big difference is the WAIT 3 statement in BASIC; during that time, the program is doing nothing else, so you can imagine that a macro that simulates your being at home would always be stuck waiting for a delay to end, unable to check other things like the temperature or a security system. With the Ocelot, the A-1 ON command simply starts a timer that runs independently from your program. Another code segment looks at the timer on-the-fly during the long loop to see if it is time for the next step, and updates conditions if so. The output X-10 command is put in an output buffer, freeing the program right away to keep looping. A better example of multitasking is shown below. This program checks if an X-10 A-1 ON command is received 3 times within 10 seconds of receiving the first one, and if so sends a B-1 ON. This would be nearly impossible to do in BASIC:
STATEMENT COMMENT IF X10 A/1 ON Command if A-1 ON was received AND Timer0 > 0 and if timer is already started THEN v0=v0+1 then increment count IF X10 A/1 ON Command if A-1 ON was received AND Timer0 = 0 but timer was not yet started THEN v0=1 then start with count of 1 THEN Timer0=1 and start timer IF Timer0 Becomes > 10 if 10 seconds have elapsed AND v0 > 2 and A-1 ON received 3 or more times THEN v0=0 then reset count THEN Timer0=0 and stop timer THEN X10 Quick On B/1 send B-1 ON command IF Timer0 Becomes > 10 if 10 seconds have elapsed AND v0 < 3 and A-1 ON received less than 3 times THEN v0=0 then reset count THEN Timer0=0 and stop timer END OF PROGRAM
Note several things here; there are two IF statements looking for the triggering input condition, and two more IF lines looking to see if the output condition is met. This covers every possible outcome (remember: "idiot proofing"). In the first two cases, we are determining if the count was already started or not by checking if the timer was started. The last two IFs do different things depending on weather the count had reached 3 or more by the time 10 seconds was up. This is a good place to introduce a warning about trying to use ELSE statements to "save" on conditional tests…lets try this to replace the last two IFs:
IF Timer0 Becomes > 10 if 10 seconds have elapsed AND v0 > 2 and A-1 ON received 3 or more times THEN v0=0 then reset count THEN Timer0=0 and stop timer THEN X10 Quick On B/1 and send B-1 ON command ELSE v0=0 else reset count ELSE Timer0=0 and stop timer END OF PROGRAM
Doesn't that look great…now you load that into your Ocelot…and discover that no matter how fast and furiously you can generate A-1 ON commands, you never get the B-1 ON you are expecting! That is because the ELSE statement is executed EVERY TIME the ENTIRE conditional statement is false (just like THENs are executed every time the statement is true) …so if the timer has not yet reached 10 seconds, you are resetting the count to 0 …and stopping the timer! Remember, the ELSE statement is not an IF (statement is true) but (AND condition is not true)…Even worse is if you were to send an X-10 command in an ELSE statement, it would be sent continuously, and your Ocelot may appear to be locked up. So the rule with ELSE statements is: ELSE statements are executed EVERY TIME the ENTIRE conditional test is false! Just make sure you understand this rule and you will find yourself using ELSE very little if at all. It can be useful for things like setting a variable to reflect the status of an input, but beware of trying to use it to output commands that produce individual events like transmitting X-10 or Infrared. The following example is OK because turning ON a relay that is already ON (or turning OFF a relay that is already OFF ) has no real effect, it's just updating the relay driver latch continuously:
IF Module#1/Point#0 Is ON if input 0 on SECU-16 is ON THEN Module#0/Point#8 Turns ON turn ON first relay ELSE Module#0/Point#8 Turns OFF else turn OFF first relay
Now, lets improve on example #2 a bit; we would like to have our B-1 ON generated as soon as the A-1 ON has been received 3 times, and not have to wait for the 10 second maximum interval to be elapsed if the three A-1 ONs were received in less than that. The last two conditional tests would become:
IF Timer0 Becomes > 10 if 10 seconds have elapsed THEN v0=0 then reset count THEN Timer0=0 and stop timer IF v0 > 2 if count has reached 3 THEN v0=0 then reset count THEN Timer0=0 and stop timer THEN X10 Quick On B/1 and send B-1 ON command
This actually simplifies the program by eliminating the ANDs since we are testing for a single parameter being satisfied. Either the count has reached 3 and thus stopped the timer and reset the count (and sent the command), or the timer has reached 10 seconds, which means our count hadn't reached 3 when the timer expired (because if it did, the timer would have been stopped). The point of this example is that the timer counts up independently of the count of A-1 ONs received.
Timers and variables make for a powerful combination, as a variable can be used as a step counter to progress through a number of timed events. Most Ocelot users want it to be able to simulate an occupied house by turning lights on or off while they are absent. I have integrated this into my security system; when I arm the alarm system, the Ocelot starts to do its sequence to make the home look occupied. This is a good example of how to use variables as status indicators and event step counters. First, here is the routine that looks for the security system being armed or disarmed (my alarm system sends a D-13 OFF when in exit delay and a D-14 OFF when disarmed):
IF X10 D/13 OFF Command * IF ALARM IS IN EXIT DELAY * THEN Transmit X10 A/All Lights OFF turn off main floor lights THEN Transmit X10 B/All Lights OFF turn off basement lights THEN v0=1 V 0 = 1 = system armed IF X10 D/14 OFF Command * IF ALARM IS DISARMED * THEN v0=0 V 0 = 0 = system disarmed THEN v1=0 V 1 = 0 (not-absent) THEN v2=0 V 2 = 0 (stop progression) THEN Timer2=0 stop progression timer
This section already shows all the timers and variables that will be used for the purpose of making the house look occupied. Variable V0 is a status indicator, it will be either 0 or 1, depending on weather the alarm system is armed or not. This can then be used by any routine that wants to take action depending on alarm system status ( like turn off water heater, lower thermostat setting, etc). V2 and Timer 2 are used as a progression counter and timer, we'll see this later. Here, we are just showing that they are stopped by setting them to zero when the alarm system is disarmed. V1 is the absent mode status indicator. A value of zero means we are not absent ( thus at home ). If we are absent, V1 will be set to either 1,2, or 3 by the following routine:
IF Day of Week = 0 if today is Sunday OR Day of Week = 3 or Wednesday OR Day of Week = 5 or Friday AND v0 = 1 and alarm is armed THEN v1=1 set absent mode to 1 IF Day of Week = 1 if today is Monday OR Day of Week = 4 or Thursday AND v0 = 1 and alarm is armed THEN v1=2 set absent mode to 2 IF Day of Week = 2 if today is Thursday OR Day of Week = 6 or Saturday AND v0 = 1 and alarm is armed THEN v1=3 set absent mode to 3
As you can see, V1 will be set to 1,2,or 3 depending on the day of the week, this is to allow running different "lived-in" routines every next day so the place doesn't look too "robotic". Now, note that V1 can only be set to 1,2, or 3 if the alarm system is armed, and that disarming it sets V1 to zero. Thus V1 being 1,2,or 3 implies that the alarm system is armed. This is important to know, because it will save us many conditional tests during the "lived-in" sequence. Knowing the exact meaning and implications of your variables makes programming a lot easier and consistent.
Now for the actual "lived-in" routine. I will only show one of the three possible "days" to save space, as only the difference is in the X-10 commands sent and the time delays used.
IF v1 = 1 *ABSENT mode 1 step 1* AND TimeOfDay > Sunset + -30 minute(s) and half hour before sunset AND v2 = 0 and progression not started THEN Transmit X10 A/4 living room THEN Transmit X10 A/11 kitchen THEN Transmit X10 A/15 den THEN Transmit X10 A/ON turn ON units THEN v2=1 set progression = 1 THEN Timer2=1 start timer 2 for progression IF v1 = 1 *ABSENT mode 1 step 2* AND v2 = 1 if progression = 1 AND Timer2 Becomes > 9600 and ( 2h 40min) has elapsed THEN Transmit X10 A/All Lights OFF turn all lights off THEN Transmit X10 A/2 entrance THEN Transmit X10 A/3 daughter's room THEN Transmit X10 A/5 bathroom THEN Transmit X10 A/6 hall THEN Transmit X10 A/13 kitchen counter THEN Transmit X10 A/ON turn ON units THEN v2=2 set progression = 2 THEN Timer2=1 start timer 2 for progression IF v1 = 1 *ABSENT mode 1 step 3* AND v2 = 2 if progression = 2 AND Timer2 Becomes > 5400 and ( 1.5h ) has elapsed THEN X10 Quick Off A/5 turn off bathroom THEN Transmit X10 A/4 living room THEN Transmit X10 A/14 laundry THEN Transmit X10 A/ON turn ON units THEN v2=3 set progression = 3 THEN Timer2=1 start timer 2 for progression IF v1 = 1 *ABSENT mode 1 step 4* AND v2 = 3 if progression = 3 AND Timer2 Becomes > 2400 and ( 40 min ) elapsed THEN Transmit X10 A/6 hall THEN Transmit X10 A/13 kitchen sink THEN Transmit X10 A/OFF turn OFF units THEN Transmit X10 A/5 bathroom THEN Transmit X10 A/10 main bedroom THEN Transmit X10 A/ON turn ON units THEN v2=4 set progression = 4 THEN Timer2=1 start timer 2 for progression IF v1 = 1 *ABSENT mode 1 step 5* AND v2 = 4 if progression = 4 AND Timer2 Becomes > 3600 and ( 1 hr ) elapsed THEN Transmit X10 A/All Lights OFF turn everything OFF THEN v2=5 set progression = 5 THEN Timer2=0 stop progression timer
Note how each IF section can only be entered if: we are in absent mode 1, we are at the correct step number; and the time duration for the previous step has been reached. See how easy it would be to add more steps, or to change the duration or commands within a step. To make the routine for absent mode 2, just copy and paste the whole routine and change the IF statements to IF v1 = 2. Also notice that when the last step is reached, the progression timer is stopped, but the progression variable is set to 5, and not zero. If it was set to zero, and midnight had not yet been passed, the whole routine would start over again because the first IF would again be satisfied. This means that we need an additional section to determine when the routine could be restarted:
IF v1 = 1 *ABSENT mode 1,2 or 3 step 6* OR v1 = 2 if in any absent mode OR v1 = 3 (V1 = 1,2,or 3) AND v2 = 5 if progression = 5 AND TimeOfDay = 300 and time is 3:00 AM THEN v2=0 set progression to 0 for the next day IF v1 = 1 *CUT ROUTINE IF LATE ENOUGH* OR v1 = 2 if in any absent mode OR v1 = 3 (V1 = 1,2,or 3) AND TimeOfDay = 2330 and time has reached 11:30 PM THEN Transmit X10 A/All Lights OFF turn OFF all "A" units THEN Transmit X10 B/All Lights OFF turn OFF all "B" units THEN v2=5 progression = 5 THEN Timer2=0 stop progression timer
When progression variable is set to 5, the "lived-in" routine has stopped progressing and is just waiting for time to be past midnight (in this case 3:00 AM) before being set to zero so that the next day's routine can be launched when its start time is reached. Also look at the "CUT ROUTINE…" section: I added that because the actual time that my lighting routines start is based on sunset, which varies widely between the summer and winter months. Each routine lasts around 6 hours or so, fine for winter but would end much too late in the summer, where sunset can be past 8:00 PM. The cut routine just looks to see that on any "absent" day, if the time has reached 11:30 PM, all lights are turned off and the progression is set to 5 and stopped, just as if we had reached the last step of a regular progression.
This rather long example (examples 5 through 8) shows that a great deal can be accomplished with just a few variables and a timer. Note that a timer can be reused many times if it's enabled or tested by mutually exclusive events (in this case, each routine is unique because of the progression counter and the absent mode number). For more random events, like a user triggered macro or input, it is best to use a timer exclusively for that routine, because the only parameter you will be testing is the timer itself (as in example #1). To use the same timer for two different such routines would cause interaction between them.
Stack Based Logic
A subject that needs some explanation is the stack based logic used with the Ocelot. This is somewhat similar to the last result on calculators, in that the internal stack always contains the result of the last calculation ( or logic test in this case ). This implies that you cannot use "parenthesis" to influence the order in which tests are done. On the Ocelot every logical test is done against the previous result (ie: there is no hierarchy of operations). Since you cannot use parenthesis, the order in which you place the tests becomes very important! Look at example 8 above: If v1 is equal to either 1,2,or 3 after executing the first three lines, the test will be TRUE at that point because any one of those lines will be satisfied. Lets examine the process in detail; suppose v1 equals 2 and v2 equals 5: The first line will test FALSE. The second line tests TRUE, being ORed with the previous result yields a cumulative TRUE. The third line is FALSE, but being ORed with a previous TRUE still gives us a TRUE at this point. The fourth line is an AND test, so it must be TRUE as well as the previous cumulative result for the THEN clause to be executed. Notice the word "previous" in each explanation…Now lets change the order of those 4 lines:
IF v2 = 5 if progression = 5 AND v1 = 1 and in any absent mode OR v1 = 2 (V1 = 1,2,or 3) OR v1 = 3
Seems to make sense doesn't it ? Using line by line analysis however: the first line and the second line will give us a FALSE result because in our test case, v1 is not equal to 1. However, these two lines could ALSO give us a FALSE result if v2 is not equal to 5 (it's an AND test, right ?). Now we get to the third line, which will test TRUE because yes, V1 is equal to 2. Since the third line is ORed with the previous result ( and so is the fourth line ), the statement could test true even if v2 is NOT equal to 5 ! Concretely, this reworked example would execute its THEN statements if both v1=1 AND v2=5; or if v1 is equal to 2 or 3 REGARDLESS of the value of v2. So remember: if you require one of several possible conditions plus some mandatory condition(s), put the OR statements FIRST !
One other point about this type of logic is that because you cannot have parenthesis, it's impossible to code for one of several AND conditions (ie: IF (a AND b) OR (c AND d)). You'll have to break this up into individual IF statements. For example ( suppose from the previous example that another valid condition would be if v1=4 and v2=6):
IF v1 = 1 *ABSENT mode 1,2 or 3 step 6* OR v1 = 2 if in any absent mode OR v1 = 3 (V1 = 1,2,or 3) AND v2 = 5 and if progression = 5 THEN bla bla bla 1 THEN bla bla bla 2 IF v1 = 4 if in absent mode 4 AND v2 = 6 and if progression = 6 THEN bla bla bla 1 THEN bla bla bla 2
This works fine, but you have to repeat the THENs (and any ELSEs) twice. If the number of these is large, or if there are several groups of AND conditions that would require the same THENs/ELSEs to be executed, you could save on the number of program lines by using a variable to hold the result of each test (here we add a third valid possibility: if v1=5 and v2=7):
IF v1 = 1 *ABSENT mode 1,2 or 3 step 6* OR v1 = 2 if in any absent mode OR v1 = 3 (V1 = 1,2,or 3) AND v2 = 5 and if progression = 5 THEN v3 = 1 set v3=1 because test is TRUE IF v1 = 4 if in absent mode 4 AND v2 = 6 and if progression = 6 THEN v3 = 1 set v3=1 because test is TRUE IF v1 = 5 if in absent mode 5 AND v2 = 7 and if progression = 7 OR v3 = 1 or one of prev. tests was TRUE THEN v3 = 0 reset variable for next tests THEN bla bla bla 1 and execute commands ... THEN bla bla bla 2
Here, any of several tests sets v3 equal to 1. The final test in the sequence looks for v3 having been set to 1 as a valid condition independently of any other test(s) of its own (hence the OR after an AND sequence). In its THEN statements, it also resets v3 to 0 so that the next iterations of the loop will be able to signal a new occurance of the right conditions being met (in LL, it is important to acknowledge that a one-time condition has been "consumed" by the targeted routine).
One-time vs Retriggered events
The last example of the previous paragraph introduces the notion of one-time events being recognized as such. This very important concept must be well understood for successful LL programming. Look at the following example:
IF TimeOfDay = 2330 if time has reached 11:30 PM THEN Transmit X10 A/All Lights OFF turn OFF all "A" units
Since our LL programs execute as a long continuous loop several times per second, you could argue that the all-lights-off command will be sent continuously during the entire minute that the time of day is equal to 11:30 PM, and you are technically correct! The way to do this only once would be to 1: Look for the time-of-day as well as a variable indicating that this is the first time we "see" the time being met. 2: Execute the command only if the variable is thus set and change the variable to indicate that command has now been executed. 3: Have another test check that the time has now been past so that the variable my be reset for the next day where the time is met again. Whew!
Fortunately, The Ocelot's language recognizes the need for tests to be normally satisfied only once per first occurance, and thus we have the "becomes" versions of the "=", "<", and ">" tests. Now, I used the IF TimeOfDay above as an example because it is an exception (and a slight inconsistency in my opinion) because it IMPLIES the "becomes" behavior. Thus example #12 really means "For the FIRST TIME that time of day becomes 11:30 PM…" All other tests that offer the regular conditional tests as well as "becomes" versions offer both possibilities. Make sure you understand and choose the right one! Here is an example of a use that could give unexpected results:
IF Timer0 > 60 after 1 minute THEN X10 Quick On A/1 turn on A/1 device
Unless another THEN statement turns off Timer 0 in the same group of THENs, this routine will send A/1 ON commands continuously as long as Timer 0s value stays above 60. To correct that without needing an explicit "timer OFF" command (because you may want the timer to keep going for some other reason), you should change the above IF statement to: IF Timer0 becomes > 60. That way, the THEN will only be executed the first time Timer 0 is greater than 60. For this line to execute again (and any other "becomes" statement for that matter), the condition has to become FALSE again. Here is an example of how both types of test could be useful; We have an analog input representing a temperature on the first input of a SECU-16. We want to turn on the first output relay of the SECU-16 to turn on a heater if the voltage drops below 3V. Since the SECU-16 will report a voltage of 0 to 5V as a value from 0 to 255, this means that an input value less than 153 should turn ON the relay, otherwise we want it OFF:
IF Module#1/Param#10 < 153 if input drops below 3V THEN Module#1/Point#8 Turns ON turn ON first relay ELSE Module#1/Point#8 Turns OFF else turn it OFF
This is also a good example of how ELSE can be used. Here, at every loop iteration, the relay will be updated to reflect the condition of the analog input, basically in real-time. Now, suppose we cannot use a relay, but have to send an X-10 command instead. Since X-10 commands only turn things ON or OFF once, we need an additional test to know when to turn the heating device OFF. This requires the following two test sequences:
IF Module#0/Param#10 Becomes < 153 if input drops below 3V THEN X10 Quick On A/1 turn ON X-10 device A/1 IF Module#0/Param#10 Becomes > 152 if input rises to 3V or more THEN X10 Quick Off A/1 turn OFF X-10 device A/1
See how we use the "becomes" version this time, to avoid having the Ocelot sending A-1 ON commands continuously as long as the input value test is TRUE. Notice how it would also be easy to introduce dead-band (hysterisis) by simply looking for a greater value than 152 in the second test statement.
Other Things You Should Know
As you program the Ocelot, some questions may come to your mind about certain conditions and how long they last. Daniel Smith of Applied Digital has provided the following information:
Received X-10 commands and status information is updated internally between program passes (loops). This means that you may look for such "one time" events as receiving a given X-10 ON command pair at several places in your code; each such test will be TRUE during the entire single pass following the status change.
At power-on, all timers are set to 0 (disabled) and all X-10 devices are deemed to be OFF. In an upcoming version of C-MAX (the programming utility for the Ocelot), there will be a parameter that will allow you to set a "cutoff" point in the variables; all variables below that point will be initialized to 0 upon power-on, all variables above that point will be retained (potentially very useful for recovering from power failures).
Ladder logic takes some getting used-to when you start working with it, but once you know the rules and possible pitfalls to avoid, you will find that you can accomplish almost anything using a rather minimalist instruction set. The only real disadvantages of a ladder logic system are that you'll find that you need many lines of code to accomplish otherwise simple tasks, and that the minimum time resolution of the system is limited by the speed of the processor, since you are always verifying the whole loop, even though most tests will be unnecessary in any given pass. These limitations are not much of a problem for a low speed application such as home automation and the Ocelot, with its 2000 line capacity, can handle quite a few tasks before the total number of lines becomes a problem. My hope is that this article will allow you to get the most out of your Ocelot and at the same time help you avoid some of the traps that ladder logic can spring upon you due to its unusual programming model.
This post does not have any comments. Be the first to leave a comment below.
Post A Comment
You must be logged in before you can post a comment. Login now.