Jump to content

ergodic

Members
  • Posts

    359
  • Joined

  • Last visited

Recent Profile Visitors

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

ergodic's Achievements

Experienced

Experienced (4/6)

1

Reputation

  1. Thanks. I'll modify the commentary on the original post to explain this. I don't usually like retroactive edits like this, but in this case it seems Since this is the defined behavior, I'd like to renew my suggestion for some kind of mini-wait (sub-second wait or that just puts current execution at the end of the queue with no other delay.) It is a much simpler way to solve this kind of thing.
  2. Thanks. So at what point is Test.C0's condition getting evaluated, relative to its body execution? I'm not quite getting it. The condition basically says "and only execute this if $Test.Code is not 1234." But the body appears to gets executed anyway when $Test.Code IS 1234. I'm sure of that because I stuck an assignment of $Test.Code to a diagnostic variable at the beginning of the Test.C0, and sure enough: 1234. Really just trying to wrap my head around why this happens, it's very counter-intuitive.
  3. I did use a non-state variable, though I should make that clear. Do I understand correctly that ISY variable assignment processing is handled asynchronously and so can interrupt the program at the point of the assignment, or is this something different?
  4. I've re-worked and re-posted the original code. In working it up again, I ran into what I think may be an ISY variable processing bug. I've noted it in the revised code posting. This workaround I'm very sure wasn't necessary with the original ISY firmware when I first did it, so something's changed. Maybe someone from UDI can shed some light on this problem. It's left me baffled, but I've spent as much time as I can to trying to figure it out. The workaround in the code I posted takes care of it in this case.
  5. It's a mess all right. No idea what's happened, it used to be OK - maybe the forum move had some impact. I'm not sure I even have that code around anymore, so I'll need to look at this when I have some time and just post a new one from scratch. Sorry and thanks for pointing it out.
  6. It is also possible to implement a variable wait using a pair of programs - one to start the process and one to 'step down' to zero. And a simple (non-state) variable to implement the wait interval. For instance, this turns on a light after $V seconds (30 seconds in this case): //___________________ //Program TestVarWait If Then $V = 30 Run Program 'Wait.V' (If) //______________ //Program Wait.V If $V > 0 Then Wait 2 seconds $V -= 2 Run Program 'Wait.V' (If) Else Set Scene 'Scene: U/S Office Desk' On The main advantage of this design is you can treat "Wait.V" as a subroutine of sorts, callable with different wait times from different programs. Note that this does lose the initial value set into $V. The main issue with this is that the "Run Program Wait.V" (at the end of TestVarWait) executes asynchronously and so execution continues immediately. That is not an issue here, but if you are waiting for the event to occur before you proceed on, you need to add logic to initialize and in effect wait for "Wait.V" to become false. This seems to require creation of yet a third program. I've tried a bit to concoct something simpler using state variables to handle this, but without any real success - maybe someone else has an idea. Even with a 1 second increment, the variability I've seen seems quite small, though I wouldn't use that for something involving hours. And since even trigger / state variables aren't used for this and the actual executions are trivial, I'd be astonished if this adds any real loading to the ISY.
  7. Yes. That sort of thing. About all you can do with regular (non-state) variables at the moment are very simple counting operations. I'm not trivializing that - it's useful, but anything much past that seems to need variable/variable assignment and compares.
  8. rhughes: 1) I think you could do a 0 init and run-at-startup, instead of the -1 init. I just started this attempt with that and left it that way. 2) I've fixed the test2.c0 which somehow got lines from one of the other programs - probably when I doing that copy-to-clipboard I just selected the wrong one. (Note to ISY: it would be really nice to have a copy-to-clipboard at the folder level.) 3) I tried several ways to boil the two occurrences of 1234 down to one. And also to try to make it a regular variable . I never found anything acceptable. The cures were all worse than the disease. If you come up with something I'd be interested to see it. Part of the problem is there is no way to do variable-to-variable assigment, so that limits things you could otherwise do.
  9. Thanks for catching that. The .C1 and .C2 programs somehow disappeared in my copy/pastes to the forum. I've edited the post to insert them and fix the ">". Hopefully that's it.
  10. The <0> is apparently what happened to ">" when the forum system got hold of it. I'll try to edit the post. The $test.state question is probably a good one. I'll have to look when I'm at the ISY console.
  11. (Note: I edited this on 10/14/12 to restore some code that was missing in this post. I also had to make changes in order to work with the current ISY firmware. So these are a little different than originally posted. I have briefly tested them again and they seem to work correctly, but comments and revisions are always welcome. My apologies for the confusion -- ergodic) OK. With the new state variables in the ISY we still keep the overall “state machine†logic, but we can improve a whole lot on the old method. Here are the essentials: (1) First, the set of programs gets an ISY state variable assigned to track and control the current state. It is important to define this as a state variable because we’ll be triggering on it. (2) Next, we still have condition and body programs as before. But each body program will now have exactly one condition: a trigger that the state variable’s value equals that state’s number. “You idiot! You just got finished above telling me how important it is not to have any conditions on the body programs.†But that’s the old way. Now we are triggering the body programs when the state variable changes value, and that is exactly the behavior we get from the condition. So when the variable value changes, the correct state is triggered and run, and the other states’ body programs go false and stop. Neat, huh? (3) Lastly, transitions between states now are accomplished simply by assigning a new state number to the state variable. We then let the ISY triggering evaluation take over and do our heavy lifting. No more endless Run Program rubbish! Here’s an example of the “KPL combination lock†programmed this way. It's a good example because it is simple but still illuminates the state method, as well as some other nice techniques using variables. If you haven’t run into this example before, it requires the four auxiliary buttons on a 6-button KeypadLinc to be pressed in some predefined sequence. Here we will use A.. B.. C.. D as the sequence and 20 seconds for the timeout. There is also a 20 second timeout if no button gets pressed to continue to sequence. If the correct sequence is entered then a light is flashed and the programs then reset. The wrong sequence or a timeout will just reset with no light flash. Before we start, there are a few prerequisites: (1) You need a recent ISY firmware version 3 that has variables. (2) You have to have a KPL with the four buttons available. (3) You have to associate each of the four KPL A-D buttons with a scene — this is an ISY/Insteon requirement to be able to control them independently. (4) We need to define a state variable to control and track the current state: I’ll be using the highly creative name: “$Test.State†here. Again, is crucial to use a state variable; we need changes in the value to trigger ISY events. (5) We're also using a second ISY state variable: $Test.Code. This is for tracking the code sequence as buttons are pressed. Since ISY variables are only integers, button A corresponds to 1, B to 2, C to 3 and D to 4. So if A... B... C have been pressed so far, $Test.Code will be set to 123. To do I’m employing an old programmer’s trick. We multiply $Test.Code by 10 and then add in the next button's value (1..4) so that each digit of $Test.Code is always tracking the sequence entered. So if we end up with 1234 that means success. Any other 4-digit value means failure. Again, it is important to define $Test.Code as a state variable because we’re triggering on its changes. We also need to create a non-state (integer) variable: $Test.Temp. This is just used to build the value we want to assign to $Test.Code before actually performing the assignment in Test.CA, etc. More on that below. There are just three states in our state machine and they’re very easy to diagram: State 0: The idle state waiting for the first button press, which when received jumps to state 1. State 1: waiting for more button presses and will timeout after 20 seconds back to state 0 if the correct code hasn't been entered by then. State 2: the success state from state 1 and flashes the light before returning to state 0. In addition there are four other little programs, all similar, one for each button press A, B, C and D. They clear the button LED and update $Test.Code for the button's value. These are not state programs, just little routines to update $Test.Code. First, the three body programs for states 0, 1, 2. Again, each body program always has one condition ("If state is value) that triggers when that state value is set. This format is required: //TEST.B0 (initial state, waiting for the first button press) If $Test.State is 0 Then $Test.Code = 0 Set Scene 'Scene: USO A' Off Set Scene 'Scene: USO B' Off Set Scene 'Scene: USO C' Off Set Scene 'Scene: USO D' Off //TEST.B1 (waiting for more presses, or times out back to state 0 after 20 seconds) If $Test.State is 1 Then Wait 20 seconds $Test.Code = 0 $Test.State = 0 //TEST.B2 (success - see TEST.C0, flash the light and return to state 0) If $Test.State is 2 Then Set 'US Office: Cans 6' On Wait 3 seconds Set 'US Office: Cans 6' Off $Test.Code = 0 $Test.State = 0 Here are the corresponding condition programs that make the explicit transitions into each state. In addition to these, each state's body program can make a transition when exiting to another state. //TEST.C0 (go back to initial state if wrong code entered while in state 1) If $Test.State is 1 And $Test.Code > 1000 And $Test.Code is not 1234 Then $Test.Code = 0 $Test.State = 0 //TEST.C1.1 (Jump to state 1 on first button press) If $Test.State is 0 And $Test.Code > 0 Then $Test.State = 1 //TEST.C1.2 (Restart timer in state 1 on subsequent button presses) If $Test.State is 1 And $Test.Code < 1000 Then Run Program 'Test.B1' (Then Path) //TEST.C2 (Go to state 2 when buttons pressed in the right sequence) If $Test.State is 1 And $Test.Code is 1234 Then $Test.State = 2 Finally, the routines that update $Test.Code on each different button press and then clear the button LED. //TEST.CA If Control 'US Office: Cans 6 / US Office: Cans A' is switched On Then Set Scene 'Scene: USO A' Off $Test.Temp = $Test.Code $Test.Temp *= 10 $Test.Temp += 1 $Test.Code = $Test.Temp //TEST.CB If Control 'US Office: Cans 6 / US Office: Cans B' is switched On Then Set Scene 'Scene: USO B' Off $Test.Temp = $Test.Code $Test.Temp *= 10 $Test.Temp += 2 $Test.Code = $Test.Temp //TEST.CC If Control 'US Office: Cans 6 / US Office: Cans C' is switched On Then Set Scene 'Scene: USO C' Off $Test.Temp = $Test.Code $Test.Temp *= 10 $Test.Temp += 3 $Test.Code = $Test.Temp //TEST.CD If Control 'US Office: Cans 6 / US Office: Cans D' is switched On Then Set Scene 'Scene: USO D' Off $Test.Temp = $Test.Code $Test.Temp *= 10 $Test.Temp += 4 $Test.Code = $Test.Temp With this approach, it also is now simple to change the ‘secret code,’ as it is just a number in two places in the program set, instead of being wired into the logic. Simply change the two occurrences of 1234 to 1144 and now the press sequence is changed to AADD. Test.C1 is split into two parts. This is because state variables trigger only when the assignment changes the value. We have to do a Run Program in the second one because the state value is already 1 and it isn't being changed. There are other ways to do this, but this seems the most straightforward. The other note regards Test.CA/CB/CC/CD. Why is $Test.Temp needed to build and then assign the final value to $Test.Code? This takes some serious explanation. So why not just assign these values directly to $Test.Code as with: //TEST.CD (The WRONG way) If Control 'US Office: Cans 6 / US Office: Cans D' is switched On Then Set Scene 'Scene: USO D' Off $Test.Code *= 10 $Test.Code += 4 In fact, I originally wrote these four programs exactly in this format, and spent quite a bit of time trying to figure out why they didn't work. The problem is subtle. It is a result of the way the ISY handles state variable assignments. Each assignment creates a trigger event. The condition clauses of each 'triggerable' program are evaluated first. Then execution of any that match get queued up, in order for processing. In this case when the Test.CD program completes, freeing the ISY to process it's pending event/trigger queue. But this leads to an unexpected behavior. Consider the case of Test.CD getting a press of "D" as the final, successful button. Test.C0 gets queued up to execute it's Then part for the (wrong) 1230 value (resulting from the *= 10 assignment.) This execution happens even though by the time Test.C0 executes, the value of $Test.Code has already been set to the correct value of 1234. The condition on Test.C0 says not to execute if the value is 1234, but that test was done when the *= 10 took place. And the second, correct, trigger on 1234 never gets the chance to fire off because Test.C0 "thinks" the wrong 4-button sequence was entered (1230) and so resets everything back to state 0 failure. Bummer. It's worth noting you can accomplish the same thing without the temp variable, but including a small, one-second wait at the start of Test.C0. That permits the ISY to restart execution of Test.C0 with the pending queued-up correct value of 1234. But the delay is needless otherwise and the temp variable approach seems more appropriate. There is some more discussion of this later on in this thread. The bottom-line rule is: don't change the value of a state variable unless and until you want it to trigger on that value.
  12. Now that variables are available, I thought I’d update my 'state diagram' approach to ISY programming to use them. ISY variables – particularly the “state variables†- are a natural fit for this. I’ve broken this into two posts. If you want to skip over the background here, the next post gets to an example of a new method using state variables. To very briefly recap the state machine method: you first diagram your logic on paper with numbered circles (“statesâ€). The states represent all the various sub-actions your programming needs. Then draw arrows labeled with the trigger events that transition between the states. Each state then breaks down into two ISY programs: (1) A “body†program with no conditions that execute the actions of that state, and (2) A “conditions†ISY program that only triggers on any of the incoming transitions to the state and then does a Run-Program on its corresponding body program. If you want to read more about this older, non-variable method to implement this, dnl started with my various haphazard, rambling posts and remarkably elaborated it all into something coherent, here: http://forum.universal-devices.com/topic/5410-triggers-and-conditions-and-ifs-oh-my/ This prior method used the true/false status of the body programs. Exactly one of the body programs was true at any time – representing the current state of the logic. But there were drawbacks: achieving this required a lot of tedious, easy-to-screw-up, copy/paste boilerplate. It resulted in obnoxiously dense programs since each body program first had to ‘false-out’ all the others. And, state transitions translated to “Run Program†statements — these can sometimes be tricky because of their asynchronous nature in the ISY. Anyone who seriously programs their ISY sooner or later come to realize why they have to break any nontrivial logic into two separate ISY programs. But it is worth restating in this case: The body program for a state MUST have no conditions so it is isolated from spurious ISY trigger events that could incorrectly cause it to be stopped. So the condition(s) for a state need to be tested/triggered by a separate program. We don’t care how often that program evaluates false because nothing gets disrupted when that happens. In effect, the condition programs insulate their body programs from events, only allowing the ‘correct’ ones through. If you try putting conditions on a body program, or statements in a condition program, you’ll quickly discover you need to be the love child of Countess Ada and Bertrand Russell to figure out what is going on. It would be nice if the ISY allowed us to simply delete the “Else†clause of a program as an indication we don’t want a program interruptible by false-state triggers – the Else section is rarely useful anyway. But we work with what we have. The next post shows a way to update this approach nicely using state variables.
×
×
  • Create New...