Jump to content

dnl

Members
  • Posts

    75
  • Joined

  • Last visited

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

dnl's Achievements

New

New (2/6)

4

Reputation

  1. Larryllix summed it pretty well. There is a link near the bottom of the first post. Follow that link to a thread that discusses using variables in the programming structure.
  2. Agreed the complexity is greater but I can see some ways where this technique might be used to advantage. 1) The delay time can be the result of a calculation. The ability to do calculations right now is limited but not non-existant. 2) The delay code can be packaged as a subroutine and called from multiple places, reducing program maintenance. Of course, it may be necessary to take care not to call the routine again before it completes. 3) I don't know whether a variable can be set directly from a REST call but I believe it is possible to do it indirectly by controlling which programs execute and having those programs set a variable as desired. There may not be many instances where we need or want to do any of these things but if we do, some approach like what ergodic suggests is needed.
  3. The advantage is that the delay time is programmable. Some day I am sure UDI will support the use of variables in a wait statement but until then something else is needed.
  4. Hi ergodic, I think your example code could use the ELSE clause to achieve some synchronization. Execution of the following code would be initiated by setting $F = 0. What do you think? //___________________ //Program TestVarWait If $F = 0 Then $V = 30 Run Program 'Wait.V' (If) Else Run Program 'DoItNow' (If) //______________ //Program Wait.V If $V > 0 Then Wait 2 seconds $V -= 2 Run Program 'Wait.V' (If) Else $F = 1
  5. Thanks Illusion. Right you are. Actually, there was more than one instance of "KPL A" in the first example and I updated the post to correct them.
  6. I also look forward to having extended capabilities for using variables. I have an approach that controls repetition and wait times with variables. I admit it is a kludge but I think it is better than nothing. For repetition: (1) set a variable (REPLIMIT) to contain the number of repetitions you want (2) code the repeat loop in your program to repeat 999 times (or some number larger than you will ever want) (3) initialize another variable (REPCOUNT) to zero just before the repeat loop and increment it by one inside the repeat loop (3) run the IF clause of another program (REPCONTROL) inside the repeat loop (4) the IF clause in REPCONTROL compares REPCOUNT against REPLIMIT; when REPCOUNT > REPLIMIT, the THEN clause stops execution of the first program. This approach requires each pass through the repeat loop to run longer than it takes to run the other program so a short WAIT statement may be needed after the RUN command. Alternatively, you could make REPCOUNT a state variable and omit the RUN statement in the loop. Each increment of the variable would invoke the REPCONTROL program. For wait: This is a similar technique ... (a) using one variable (WAITLIMIT) to contain the length of the wait that you want, ( a repeat loop in your program to repeat some very large number or times © using another variable (WAITCOUNT) to count loops (d) running the IF clause of another program (WAITCONTROL) inside the repeat loop (e) putting a WAIT statement inside the repeat loop. I have not tried this but I suspect it will not be possible to achieve precise control for some small number of seconds but it should work fine for larger increments such as multiples of two- or three-second intervals.
  7. Edited to remove a duplicate post.
  8. Hi vbphil. I have been thinking about updating this post to cover the very thing you mentioned. The introduction of IF conditions using non-state variables allows for conditional logic that is not affected by changes in state. There are also a few optimizations that could be added. Maybe after taxes are done ...
  9. mlennox, You are very welcome but the real credit is due to ergodic. He's the creator -- I only did some editing.
  10. ISY Programming Methodology (state-machine approach) edited from posts by ergodic in June 2010 The ISY uses what technically would be termed "functional" (i.e.; stateless, variable-less), "event" programming and that is not the way people think at all. So you need some methodology that bridges that gap. Ad-hoc programming in the ISY just doesn't work for me. If you can identify what you want to do and diagram it, then have some rote mechanical method to develop the ISY programs from that, it is much, much easier to get something that works, that you are sure should work at least, and also can be expanded or changed later without introducing errors that are difficult to find and correct. The end programs may not be pretty to look at, but what ISY programs are? I'll describe a method that is easy to understand, quick, and once you've outlined your logic on paper, it is straightforward to produce a set of ISY programs that implement it reliably. I'll start with a simple example: Press a keypad button ("KPL D") three times in succession to turn on a light. If more than 5 seconds elapses between presses, then the sequence resets and you have to start over.I use something like this for my garage door open programming: it prevents the door from opening if you happen to hit the KPL button by accident. You can also have a little fun with this idea, for example, turning the aux buttons of a KPL into a combination lock. I'll also note that to actually do this, you may know that Insteon requires we create a scene ("Scene KPL-D") with the button in it, just to be able to toggle the keypad light on and off from the ISY. To start, draw big, labeled circles to represent all the different "states" of your process. These are generally the places where you are "doing" or "waiting" for something. For example, "the garage door is open and we're waiting for the close button." Each situation is different but generally the states will fall out pretty quickly once you've grasped the idea and you start to diagram the flow of your particular logic. States generally map to a particular point of logic: (e.g.; "My garage door was opened by a motion sensor and we're waiting for the off from the motion sensor.") And you'll always have a starting "quiet" state that does nothing. In this example the states are pretty easy to see, there are four: a start/reset state, a state for the button having been pressed once, pressed twice, and pressed three times. We'll use labels S0, S1, S2 and S3. Then you draw arrows between the states. Label the arrows with the triggers that cause that particular state change. Anyone with an information science background will immediately recognize this as a basic "state machine". No matter, just diagram how your logic flows from one state to the next. Don't worry about the ISY just yet. And don't worry if your logic isn't fully developed, you can always go back later and expand on it. For this little example, we draw an arrow out from the edge of the S0 circle to the edge of the S1 circle, indicating a press of KPL-D when you're in state S0 takes you to state S1 (first button press). Likewise S1 to S2, and S2 to S3. These are of course all basically the same transitions, but the only way a state-based system has to "remember" anything is by the state it is in. We don't need no stinkin' variables or, more precisely, the states themselves are our variables. This is partly why this model works so well for the variable-less ISY. The important thing to understand about state diagrams is that you have no idea how you got to the state you're in. If you need to "know" that for your logic, you create more states. Inside or near each state's circle you note what that state does when it is activated. For example, S1 turns off the KPL light to indicate it is ready for another press, waits 5 seconds and then, assuming nothing else has interrupted it, it returns to state S0. S1, S2 and S3 all will have arrows back to S0 representing this "timeout" transition (i.e.; you waited too long to press a key and the state reverts to S0). I like to draw these explicit transitions from inside the circle. I'll also point out that in a less trivial example than this, for example, one involving motion sensors, you often have arrows going from a state back to itself. For example, if a motion sensor sends a repeated "on" signal while you are waiting in a state, you want to restart the timeout countdown in that state by re-entering the state. Now, if you've suffered with this explanation this far, I recommend you try implementing this in your ISY to get a feel for the process. The basic idea is to separate figuring out what you want from the actual ISY programming. Now that we have our diagram, we can easily implement our logic by set of ISY program from a rote procedure. Each state will be represented by two ISY programs. One program will test the conditions, which correspond to all the incoming "arrows" to that state that represent the various trigger conditions into it. A second program performs whatever that state is supposed to do. Here were are at the eternal sticking point: Why two programs for one state? All we need is one set of conditions and one "Then" body to execute it. So ... one program, right? Wrong. The ISY executes either the Then or Else body of a program whenever any of its trigger conditions are TESTED. (This means the Then or Else body of a program will be interrupted and restarted if that program is currently executing a Wait or Repeat command.) This causes the program to be reset and could be (often is) really a Bad Thing; we want the code for our state to run only when one of the trigger conditions (incoming state changes) is True, not each and every time they are simply checked by the ISY. If you don't understand why, keep reading and thinking about it until it makes sense. It is critical to understanding the difference between the way you might think and the way the ISY thinks. In this example, we end up with eight programs for our four states. We'll call them here S0Cond, S0Body, S1Cond, S1Body, and so forth. The structures of the four S#Cond programs are basically the same; just tests for conditions of the incoming triggered transitions that activate into that state. If any of the conditions are True, the "Then" part calls the corresponding S#Body for that state. You must run a separate S#Body program. Do not put your state code in the "Then" part of the S#Cond program. It might work after a fashion but it will not consistently work the way you want. So here's the basic skeleton of our S1Cond program: If Program 'S0Body' is True And Control 'KPL D' is switched On Then Run Program 'S1Body' (Then Path) The condition program connects each transition coming into this state with a pair of "AND" ed tests. One test identifies the state you're coming from and the other test identifies the event causing the transition from that state to this one. This is simpler to do than to explain. In this example there is just one transition into S1 from state S0. If there are multiple transitions coming into the state, you merely "OR" a set of these parenthesized tests together to trigger any of the possible incoming transitions that can get you to this state. The ISY nicely takes care of checking these conditions automatically any time a transition might happen. What is put in an S#Body program? The first thing are statements that set all of the other state's body programs False. This cancels whatever those programs might be doing at the time a transition is made to this state, which effects the actual transition to this state. Remember, we can only be in one state at a time. That also declares the other state programs as being False to the ISY. This is crucial because we are using the True/False status of the S#Body programs to define what state we are in. After setting all other state body programs to False, an S#Body program does whatever you want to be done in the state (if anything). Finally, an S#Body program performs any "tail" transition that happens if the state times out or exits before any other condition triggers the ISY to cancel it. These "tail" transitions are performed by calling the body program for the target state. This is usually the last thing that an S#Body program does. They are not included in the trigger condition list for the target state's S#Cond tests. All other incoming (triggered) transitions are included in those tests but these are programmed transitions, not events. That's why I draw those arrows starting from inside the circle, as they are implemented differently, Here's our S1Body for example: If - No Conditions Then Run Program 'S0Body' (Else Path) Run Program 'S2Body' (Else Path) Run Program 'S3Body' (Else Path) Set Scene 'Scene: KPL-D' Off Wait 5 seconds Run Program 'S0Body' (Then Path) Again, generating this program is more or less a rote process. In fact I typically first just create one template "cond" and one template "body" and use the ISY to copy and edit since they're all so similar. You also want to use a careful (and terse) naming system to keep things clear. I recommend using a common prefix so that you can more easily watch them grouped together in the ISY Program Summary. The only things to think about for the S#Body programs are how long to wait before making an explicit timeout transition out of the state, and what to do around that waiting. For the most part, you have the freedom to do whatever you please without worrying about strange interactions because you know the program isn't going to be interrupted - its execution is 'protected' by its companion S#Cond program. You may have no timeout inside a state. For example, here's the S3Body: If - No Conditions Then Run Program 'S0Body' (Else Path) Run Program 'S1Body' (Else Path) Run Program 'S2Body' (Else Path) Set 'Our Test Light' On Set Scene 'Scene: KPL-D' Off Run Program 'S0Body' (Then Path) This state just notes that it is in state S3 then resets the KPL button off, turns on the target light ("success!") and transitions back to the resting state S0. (The S0Body does nothing except turn off the other three states and exit itself, staying True.) Finally, we need something to initialize the state of this logic to S0 when the ISY boots up. We require exactly one of the S#Body programs to be "True" at any time, which indicates the state we are in. We do this by simply marking the S0Body program as the only program in this group of programs to "run at startup". If you want extra insurance, you can create a separate program that is triggered by all four S#Body programs being False and, when True, runs "S0Body (Then)". This usually is not necessary but it does take care of unusual conditions such as someone manually setting S0Body False from the ISY console. Here's another example: you want to turn your Denon amplifier off at 11:45 and turn it back on at 7AM but only if it was on at 11:45 in the first place. This application has two states: the beginning/idle state that we'll call Denon.S0 and a state Denon.S1 that represents the amp being on at 11:45, waits for 7AM to turn it off, and returns to state Denon.S0. Two states require four programs ... Oops, not quite. We have no easy way in the ISY to wait INSIDE a program for a specific time of day. There are ways you could cook up to shim around this problem such as a little program to trigger True at 7AM and make this test in a repeat loop. But all that nonsense is precisely what we're trying to get away from. The solution, as always here, is simply to add another state. With this change, the state Denon.S1 merely serves to record that the amplifier was on at 11:45 and exits doing nothing except staying True. A new third state, Denon.S2, triggers from Denon.S1 at 7AM, turns off the amplifier and returns to state Denon.S0. Now we have three states, which requires six programs. Denon.S0.Cond: If - No Conditions Then Run Program 'Denon.S0.Body' (Then Path) Denon.S0.Body: (set to run-at-startup) If - No Conditions Then Run Program Denon.S1.Body (Else Path) Run Program Denon.S2.Body (Else Path) Denon S1.Cond: If Program Denon.S0.Body is True And Time is 11:45PM and Status 'AMP' is On Then Run Program 'Denon.S1.Body' Denon.S1.Body: If - No Conditions Then Run Program Denon.S0.Body (Else Path) Set 'AMP' Off Run Program Denon.S2.Body (Else Path) Denon S2.Cond: If Program Denon.S1.Body is True And Time is 7AM Then Run Program 'Denon.S2.Body' Denon.S2.Body: If - No Conditions Then Run Program Denon.S0.Body (Else Path) Run Program Denon.S1.Body (Else Path) Set 'AMP' On Run Program Denon.S0.Body (Then Path) OK, let's take the gloves off. I hear you: "But I can do this little thing in 3 programs (or 2 or 1 or one-half a condition test with my eyes closed, or whatever). Why 6? You've got programs doing basically nothing - we can get rid of them." Yeah, yeah, yeah. If you want to chase the infamous "Busy Beaver" problem, far be it from me to dissuade you. Enjoy yourself. But if you've followed me this far, I'd guess you already know from experience that with the ISY, madness lies down that road. Maybe here you can indeed toss out "Denon.S0.Cond" but maybe later you'll really wish it were there when you make some enhancement, Like the one I make below. So my advice: stick with the structure. The main point here is that, using this technique, everything is simple and more or less as understandable - at least as understandable as anything can be with ISY programs. If my logic diagram does what I want, I can more or less be assured that the programs I generate from it this method will implement it correctly. If I have to change the logic or add more states, I know exactly what to do and where to look to do it. I don't have a Denon amp to test this with but I am still reasonably confident that - absent any typos - the above set of 6 programs will do the job exactly as described without endless fiddling and forum posts. Here's an illustration of how this technique can save some heartburn. Suppose we want to enhance this application a little. If someone turns on the amp between 11:45 and 7AM, then we want to reset the logic and not turn the amp back on at 7AM. This is done easily. Just add a transition arrow from state Denon.S1 to Denon.S0, triggering on the condition "if AMP is switched on". Put that condition into the Denon.S0.Cond program and you're done. Let's apply the technique to an application described in a post on this forum: Between 10PM and sunrise ¦ if we get either of two off signals but with either of two "test" lights still on we turn off a few lights. If neither of those two lights are on then we turn off a lot of other lights. From resting state S0, we have a transition to a state S1 between 10PM and sunrise if either of the two test lights are also still on. We have a second state S2 activated from S0 between 10PM and sunrise if neither of the two test lights is on. There are a couple of other ways to structure this state diagram but, however you graph it, you should be able to produce the 6 programs to implement it easily. Again, if you're tempted to "optimize" them down to 4 or 2 programs, my unequivocal advice is to resist that temptation. Now this technique is hardly perfection. You can still have race conditions in your code, unexpected device interactions, Insteon signal collisions or missed signals. And the ISY has no variables or macro substitution so you can easily get copy/paste errors if you change something in one place and don't mirror it somewhere else where it needs to be. All the usual stuff. But it is much, much easier to debug and solve these problems inside this kind of structure. It's kind of cool to watch the True indicator jump from program to program (state to state) in the Program Summary screen as the programs run. I always know exactly what the programs should be doing and why. A poor man's debugger. If I see two True body programs, I know I probably added a state and forgot to clear it in one of the other states. Here is a program set I have for my kitchen. There are two motion sensors. At night, turn up the lights over the kitchen island to 50% if they're off or less than that when somebody walks in. Regardless whether it is day or night, after 15 minutes of no activity shutoff all the kitchen lights using a scene. There are four states: S0: Resting state Go to S1 when motion is detected and it is night Go to S2 when motion is detected and it is not night S1: Nighttime state Turn up the island light Go to S2 S2: Waiting-for-both-MS off state Go to S3 when status of both MS goes to 'off' (failsafe) after 4 hours, go to S3 (haven't really thought this through) S3: Waiting-for-either-MS-on state Go to S2 when on is received from either MS Otherwise after 15 minutes turn off the lights and go to S0 Here's a state diagram: (motion and night) S1 <--------- S0 <---+ | (motion / | | and not / | | night) / | v / | +-------} S2 <-----+ | | | | | | (both MS off) | | | | | v | | S3 | | / \ | | / \ | +------+ +----------------+ (either (after 15 min) MS on) The programs that implement this application are shown below. There are two oddball programs. The first is the "IsNight" program that has no code for either Then or Else. The program is True or False depending on whether it is "nighttime". I use this because the definition of "nighttime" is used in two conditions and I want to be sure if I change it later I don't need to remember to do it in more than one place. The second is the "Body.S1.Sub" program. I need something to test whether the island lights are currently below 50% and only raise them in that case. Otherwise, if the lights were already brighter, they'd get dimmed -- far more annoying than you might think if you've never actually had it happen to you. The ISY has no "if...then" testing inside program clauses, which would be most welcome. So I break out this little routine that is called from S1 to check the lights and raise them only in they are below 50%. You could do this with an added state or two but it really isn't necessary. Here are the programs Kit Motion.Cond.S0: If - No Conditions Then Run Program 'Kit Motion.Body.S0' (Then Path) Kit Motion.Body.S0: (set to run-at-startup) If - No Conditions Then Run Program 'Kit Motion.Body.S1' (Else Path) Run Program 'Kit Motion.Body.S2' (Else Path) Run Program 'Kit Motion.Body.S4' (Else Path) Kit Motion.Cond.S1: If Program 'Kit Motion.Body.S0' is True And Program 'Kit Motion.IsNight' is True And Program 'Kit Motion.IsMotion' is True Then Run Program 'Kit Motion.Body.S1' (Then Path) Kit Motion.Body.S1: If - No Conditions Then Run Program 'Kit Motion.Body.S0' (Else Path) Run Program 'Kit Motion.Body.S2' (Else Path) Run Program 'Kit Motion.Body.S4' (Else Path) Run Program 'Kit Motion.Body.S1.Sub' (If) Run Program 'Kit Motion.Body.S2' (Then Path) Kit Motion.Body.S1.Sub: If Program 'Kit Motion.Body.S1' is True And Status 'Kitchen Island' < 50% Then Set 'Kitchen Island' 50% Kit Motion.Cond.S2: If ( Program 'Kit Motion.Body.S4' is True And Program 'Kit Motion.IsMotion' is True ) Or ( Program 'Kit Motion.Body.S0' is True And Program 'Kit Motion.IsMotion' is True And Program 'Kit Motion.IsNight' is False ) Then Run Program 'Kit Motion.Body.S2' (Then Path) Kit Motion.Body.S2: If - No Conditions Then Run Program 'Kit Motion.Body.S0' (Else Path) Run Program 'Kit Motion.Body.S1' (Else Path) Run Program 'Kit Motion.Body.S4' (Else Path) Wait 4 hours Run Program 'Kit Motion.Body.S4' (Then Path) Kit Motion.Cond.S4: If Program 'Kit Motion.Body.S2' is True And Program 'Kit Motion.IsMotion' is False Then Run Program 'Kit Motion.Body.S4' (Then Path) Kit Motion.Body.S4: If - No Conditions Then Run Program 'Kit Motion.Body.S0' (Else Path) Run Program 'Kit Motion.Body.S1' (Else Path) Run Program 'Kit Motion.Body.S2' (Else Path) Wait 15 minutes Set Scene 'Scene: Kit On' Off Run Program 'Kit Motion.Body.S0' (Then Path) Kit Motion: IsMotion If Status 'Kitchen: Motion (Oven)-Sensor' is On Or Status 'Kitchen: Motion (Sink)-Sensor' is On Then - No Statements Else - No Statements Kit Motion: IsNight If From Sunset - 30 minutes To Sunrise + 30 minutes (next day) Then - No Statements Else - No Statements Kit Motion: Reset State (just for debugging, not needed) If Program 'Kit Motion.Body.S4' is False And Program 'Kit Motion.Body.S2' is False And Program 'Kit Motion.Body.S1' is False And Program 'Kit Motion.Body.S0' is False Then Run Program 'Kit Motion.Body.S0' (Then Path) ISY programming is not simple. This is illustrated by the Kit.Motion.Body.S1.Sub substate. One issue is whether the Kit.Motion.Body.S1.Sub substate has to be called explicitly. If it is not called explicitly, the ISY may never get a chance to invoke it because S1 runs through without any wait. (It isn't disabled.) It has been suggested that if Kit Motion.Body.S1.Sub is enabled, then it should run either Then or Else (as 'Kitchen Island' is, or is not, less than 50%, respectively) as soon as Kit Motion.Body.S1 becomes True. This happens as soon as its Then is called, which in this case is from Kit Motion.Cond.S1. But Kit Motion.Cond.S2 also becomes True as soon as Kit Motion.Body.S1 is True, and then calls Kit Motion.Body.S2, which will make Kit Motion.Body.S1 False. It is possible that this could create a race condition; this could be tested simply by removing the call. In summary, this method of boils down to the following steps: 1) Draw your diagram. Begin by drawing a circle for the resting state and begin thinking about what events trigger out of that. The rest will flow from that fairly quickly although it may take a few tries to get the logic right. 2) For every state in the diagram you will have two ISY programs: a body.state and a cond.state. Exactly one body.state program can be True at a time; that tells what state you are currently in. 3) The body.state program has no conditions, just a "Then" clause. It first sets all other body.state programs False. It then does whatever you want that state to do, if anything. Sometimes a state merely distinguishes where you are in the flow and may do nothing else. There is always a starting, rest state that usually does nothing and should be set to run-at-startup. If the state requires a wait or a wait is acceptable, then you can transit directly to the next state from the last statement ("Run Program Body.Target (Then Part)" instead of using a condition on the target state. The same is true if there is only one transition out of the state. Otherwise I believe you have to give the ISY a chance to check the other transit-out conditions. You must use conditions to handle every exit path. If there's another / better way to handle this, I'd be most eager to know it. 4) The cond.state lists the condition(s) for all incoming transitions into that state. It ANDs the source state = True with whatever condition takes you from that source state to this target state. This corresponds to a transition arrow on your diagram. If there is more than one incoming transition to a state, they are simply "OR"ed together to create one test. The "Then" clause for a cond.state has a single statement executing that state's body program. You should resist the temptation to put anything else in here. 5) If you find yourself using the same tests repeatedly, consider making a separate True/False program like the .IsNight and .IsMotion programs above. EDIT 8/15/11 to correct a few typos in the first example. Thanks to Illusion for pointing out the errors. EDIT 2/28/15 to clean up text by restoring some quotation marks and apostrophes that were converted into weird character strings. EDIT 8/10/15 to fix state diagram.
  11. I appreciate the patience others have shown in trying to help me (a newcomer) understand the challenges in ISY programming that arise because all conditions appearing in an IF clause are also trigger events that cause the program to run and the IF clause to be re-evaluated. It was not until I read a lengthy post by ergodic (linked from the Wiki) that the light began to come 'on' (no pun intended). The concept of the ISY acting like a state machine has helped me understand like nothing else has. Unfortunately, that post contains revisions and dialog over some fine points that make it more difficult to understand. One person (dbroere) wrote the following: I have tried to sort it out by editing that thread (I hope ergodic does not object), replacing some of the original programs with his subsequent revised programs and by redrawing a state diagram to fit the revised programs. I have taken a few editorial liberties and added some text but otherwise the text is essentially as ergodic wrote it. I may have introduced some errors. I hope others who are more experienced than I (perhaps ergodic himself) will review and correct whatever errors I have made so that we newbies can have an easier document to read. I can provide the document as a MS Word file or as an HTML file if anyone would like it. Otherwise, the edited text follows in the next post. EDIT: Version 3 of ISY firmware allows the use of integer-valued variables in our programs. The variables come in two flavors: state variables and non-state variables. Both state and non-state variables can be used to great advantage, simplifying the structure of the state-machine programs that are described below. Ergodic has described how the state variables can be used. You can see that post here: http://forum.universal-devices.com/topic/6055-state-machine-programming-using-variables-part-1/. Even if you are running version 3, I believe you will find the following post provides useful background that will help understand the new approach. EDIT 8/10/15 to fix broken link.
×
×
  • Create New...