One Button - 20%, 40%, 60%, 80%, 100% - OFF steps (Amazon Dash Button)

Hi,

I need help. I want to create a rule for my Amazon Dash Button (One Button) with different steps.
First press on the button set to 20% next to 40%, 60%, 80%, 100% and OFF
I have this simple rule but I don´t know how to create and extend the rule

rule "DashButtonXYZ"
when
    Channel "amazondashbutton:dashbutton:XXXXXXXX:press" triggered
then
    if (Light_gc_Office_Deckenfluter.state == 20) {
	    Light_gc_Office_Deckenfluter.sendCommand(OFF)
	    } else {
	    Light_gc_Office_Deckenfluter.sendCommand(20)
	    } 
end

Is the Amazon Dash Button supported by the binding? If not a possible work around would be to use an alexa routine. Before making rules I would verify that you can use the button by simply turning on and off the light.

The Dash Button is supported by the “Amazon Dash Button Binding” and I have a few of the Buttons. The above rule works perfect, but I can’t expand it to the different light levels

Good to know the button is supported and works.:+1: I don’t have a button to test with but I’m sure we can create a rule that will work. Just need to think about the best approach else if the light is at 20% and you want it off then 5 more button pushes will be needed.

@koboldmaki slide this in and see how it reacts. I know it’s incomplete but I’m thinking maybe a switch case may be a better route.

then
var lum = Light_gc_Office_Deckenfluter.state as Number
if(lum != 20 || lum != 40 || lum != 60 || lum != 80 || lum != 100){
	    Light_gc_Office_Deckenfluter.sendCommand(ON)
        }
if (Light_gc_Office_Deckenfluter.state == ON && lum != 20) {
	    Light_gc_Office_Deckenfluter.sendCommand(20)
	    }
		else if(lum == 20){
			Light_gc_Office_Deckenfluter.sendCommand(40)
		}
		else if(lum == 40){
			Light_gc_Office_Deckenfluter.sendCommand(60)
		}
		else if(lum == 60){
			Light_gc_Office_Deckenfluter.sendCommand(80)
		}
		else if(lum == 80){
			Light_gc_Office_Deckenfluter.sendCommand(100)
		}
		else if(lum == 100){
			Light_gc_Office_Deckenfluter.sendCommand(OFF)
		}
end
1 Like

Could be much more concisely implemented using:

then
    // Assume current dim is 0 if Item is NULL, light will turn on at 20%
    val curr = if(Light_gc_Office_Deckenfluter.state == NULL) 0 else Light_gc_Office_Deckenfluter.state as Number

    val newDim = curr + 20 // calculate the new dim value
    val newState = if(curr <= 100) ON else OFF // determine if the light should be ON or OFF

    Light_gc_Office_Deckenfluter.sendCommand(if(newState == OFF) OFF else newDim)

end

If the Light Item is NULL, the the Light will turn on and dim to 20%.

Between 0% and 80% the dimm will be set to 20% higher.
Over 100% the light turns OFF.

This makes use of the trinary operator (one line if else). When the condition is true, the first value is returned otherwise the value after the else is returned.

This is one of those cases I’ve been really trying to figure out how to write a tutorial about. It might be one of those things that just comes with experience. The beginner programmer will look at the OP and think “OK, we have 6 cases so lets have 6 if else statements.” An intermediate programmer will think, “nah, a switch statement will be shorter and easier to read.” The experienced programmer says “We just add 20% every time and have a couple of edge cases to deal with in if statements.” How can I teach developers to get to the last one?

2 Likes

@rlkoshak Kinda funny you replied to this topic b/c I was just kicking around the idea of reaching out to you.:smile: What I posted just keeps me coming back, looking at it and thinking, that’s awful! I had put something together using a switch statement but was waiting to hear back from the OP about the first “beginner” attempt.:grinning:

Everything I’ve learned thus far is by reading and playing with different examples but I see very few that use edge cases. Maybe I should rephrase and say few examples that are understandable.:thinking:

Now I have a real life example with a rule that’s short and practical. By the way, have I ever mentioned how much I dislike Foo and Bar?:stuck_out_tongue_winking_eye: I know it’s just a place holder so to speak, but when I first started reading it nerved me a bit. Can’t speak for everyone, but I learn best with short, practical examples.

For the book, I suggest it as you described it above. I had to start with a ton of if and else After that, switch statements slowly began to make sense and I’m still working toward a better understanding of switch. Honestly, it didn’t take long to throw the ugly rule up, but it took a bit of thinking on the switch. One of my pitfalls, for the book’s sake, is reading a post that describes the problem using programming lingo. I start googling these terms, find some examples and …< we need an emoji of a rabbit hole.:smirk:
When I get back to the forum, after reading all about these words, I’m completely lost. What part of the rule is the “Object”, what effects what, if using a “Primitive”, how to tell when it’s best to use the “Abstract”, or what “Method” are they talking about. BTW I was going for the longest run on sentence.:grinning: but I’m sure you understand where I’m coming from.

Would it be possible to have something similar to the little robot feller (forum guide) for rule’s? Example rule with explanation and a task or rule to complete before moving on.

As always, Thanks Rich

1 Like

Almost every time you see an if statement it is an edge case. Like the above, the “default” is to add 20% to the dimmer value. The edge cases are:

  • what to do if the light’s state is NULL
  • what to do if the light’s new dimming value is over 100 after adding 20 to the existing?

Another common edge case is midnight. Because Astro calculates the sunrise and sunset times for today only around midnight, you can’t simply say if(now.isAfter(sunset) && now.isBefore(sunrise)) because sunrise and sunset are both calculated for TODAY but you need sunset today and sunrise tomorrow. Any other time you want to compare between two times that if statement but if the two times span midnight you have to treat it different: edge case.

Well, it is a convention that has been with us almost from the beginning of computing. From wikipedia:

The use of foo in a programming context is generally credited to the Tech Model Railroad Club (TMRC) of MIT from circa 1960.[1] In the complex model system, there were scramswitches located at numerous places around the room that could be thrown if something undesirable was about to occur, such as a train going full-bore at an obstruction. Another feature of the system was a digital clock on the dispatch board. When someone hit a scram switch, the clock stopped and the display was replaced with the word “FOO”; at TMRC the scram switches are, therefore, called “Foo switches”. Because of this, an entry in the 1959 Dictionary of the TMRC Language went something like this: “FOO: The first syllable of the misquoted sacred chant phrase ‘foo mane padme hum.’ Our first obligation is to keep the foo counters turning.”[5] One book[ which? ]describing the MIT train room describes two buttons by the door: labeled “foo” and “bar”. These were general purpose buttons and were often re-purposed for whatever fun idea the MIT hackers had at the time, hence the adoption of foo and bar as general purpose variable names.

It’s just like using the term “widgets” in business or manufacturing. You can pretty much guarantee that it will never go away. Foo bar and Hello World are two things you will just have to get used to.

The big problem is teaching how to recognize the patterns. How do you teach someone to see “One Button - 20%, 40%, 60%, 80%, 100% - OFF steps” and think “OK, add 20 every time and turn off when it gets above 100.” That is the sticking point because the problem won’t always be stated quite so clearly and the patterns are not always so easy to recognize.

Maybe I can just punt and say “if you are seeing a lot of if/else clauses or switch cases that all do something very similar, look for a pattern.” That just doesn’t feel like enough.

Unfortunately the answer to a lot of questions like that depend on the language. At a high level an Object is a bundle of data and methods. Anytime you see a name.name pattern you are dealing with an Object. Pretty much everything in the Rules DSL is an Object.

Objects are defined by a Class. Think of a Class as being a blueprint for Objects. The Class defines what data and functions get packaged up into an Object, but an Object is an instance of a Class. An Object can be defined by more than one Class.

In the above code, curr, newDim, newState, ON, OFF, and Light_gc_Office_Dechenfluter are all Objects. I’m pretty sure that 20 and 100 are Objects too.

Primitives are pieces of data that are not an Object. They have no methods. They are just a naked piece of data. If I wanted to turn newDim into a primitive I’d use

val int newDim = (curr + 20).intValue

In the Rules DSL I recommend staying away from primitives unless absolutely necessary.

Anyway, the thing is we HAVE to use these programming terms of art. It is nigh impossible to talk about this stuff without them. It’d be like saying that a mechanic can’t use the terms tires, pistons, spark plugs, or engine. It’d be almost impossible to talk to them about a problem with a car.

I don’t know. The Rules section of the Beginner’s User’s Tutorial was supposed to be close to that. At least it was supposed to gradually build up the Rules that are found in the Demo config. As you can see, the Beginner’s Tutorial needs lots of attention.

Yep, dislike Hello World just as much and the other one not mentioned “boiler plate”. Oh, it’s just some boiler plate code, or just add this to the simple boiler plate code. But your right, just something ya gotta live with.:roll_eyes:

I completely agree and like your mechanic analogy. I mentioned it just to point out how frustrating it can get at times. Mostly the times when you really think you understand something, then nope, wrong answer.:hushed:

As for the rest of your reply, like many others,:smile:I gonna read 9.3 more times today and start off tomorrow reading it again. Then look at how to use it with what I have.

Thanks again

Hi H102,

thanks for your help and the Rule. But the rule sadly works not correkt. The bulb switch from 0 to 100 (ON) and from 100 to 0 (OFF) I have modify a little bit the rule (see below) and now it work, but not fine. Every push the button the bulb switch short to 20% and then to the next step. For me ist is okay and a workaround. Perhaps you can optimize the rule? Thanks a lot

2018-10-04 17:01:25.687 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41abc:press triggered
2018-10-04 17:01:27.172 [ome.event.ItemCommandEvent] - Item ‘Light_gc_Office_Deckenfluter’ received command ON
2018-10-04 17:01:27.188 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 0 to 100
2018-10-04 17:01:36.254 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41abc:press triggered
2018-10-04 17:01:36.280 [ome.event.ItemCommandEvent] - Item ‘Light_gc_Office_Deckenfluter’ received command ON
2018-10-04 17:01:36.323 [ome.event.ItemCommandEvent] - Item ‘Light_gc_Office_Deckenfluter’ received command OFF
2018-10-04 17:01:36.333 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 100 to 0

then
        var lum = Light_gc_Office_Deckenfluter.state as Number
if(lum != 0 || lum != 40 || lum != 60 || lum != 80 || lum != 100){
	    Light_gc_Office_Deckenfluter.sendCommand(20)
        }
if (Light_gc_Office_Deckenfluter.state == ON && lum != 20) {
	    Light_gc_Office_Deckenfluter.sendCommand(20)
	    }
		else if(lum == 20){
			Light_gc_Office_Deckenfluter.sendCommand(40)
		}
		else if(lum == 40){
			Light_gc_Office_Deckenfluter.sendCommand(60)
		}
		else if(lum == 60){
			Light_gc_Office_Deckenfluter.sendCommand(80)
		}
		else if(lum == 80){
			Light_gc_Office_Deckenfluter.sendCommand(100)
		}
		else if(lum == 100){
			Light_gc_Office_Deckenfluter.sendCommand(OFF)
		}
end```

Hi rikoshak,

thanks for your updated rule. I get this error message:

2018-10-04 17:18:24.260 [ERROR] [ntime.internal.engine.RuleEngineImpl] - Rule ‘DashButtonDesperados’: An error occurred during the script execution: Could not invoke method: org.eclipse.smarthome.model.script.actions.BusEvent.sendCommand(org.eclipse.smarthome.core.items.Item,java.lang.String) on instance: null

Can you help me to fix the problem, or do you know what is wrong?
Kind regards

I changed the sendCommand in the first if statement from 20 to ON. Wish I had a button to test with so I could confirm it works.:expressionless: Try the change and see if it works.

then
        var lum = Light_gc_Office_Deckenfluter.state as Number
if(lum != 0 || lum != 40 || lum != 60 || lum != 80 || lum != 100){
	    Light_gc_Office_Deckenfluter.sendCommand(ON)
        }
if (Light_gc_Office_Deckenfluter.state == ON && lum != 20) {
	    Light_gc_Office_Deckenfluter.sendCommand(20)
	    }
		else if(lum == 20){
			Light_gc_Office_Deckenfluter.sendCommand(40)

Edge cases are new to me so can’t say for sure what the issue is but try adding another = between state and NULL like this:

 // Assume current dim is 0 if Item is NULL, light will turn on at 20%
    val curr = if(Light_gc_Office_Deckenfluter.state === NULL) 0 else Light_gc_Office_Deckenfluter.state as Number

Three === are only required for null, not NULL. They are not the same thing.

This is pretty much always a type problem.

Break up the last line a bit. Because OFF and newDim are of different types it may not like the mixing up of them in the trinary operator like that.

    if(newState == OFF) Light_gc_Office_Deckenfluter.sendCommand(OFF)
    else Light_gc_Office_Deckenfluter.sendCommand(newDim)

If that doesn’t work, add logging to see what newState and newDim are set to before executing this code.

When I edit the rule I have no error in the Log, when the rule is loading and all steps works good, but the last step from 100 to OFF doesn’t work.

2018-10-04 21:11:31.596 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41a7a:press triggered
2018-10-04 21:11:44.657 [ome.event.ItemCommandEvent] - Item 'Light_gc_Office_Deckenfluter' received command 20
2018-10-04 21:11:44.675 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 0 to 20
2018-10-04 21:11:51.376 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41a7a:press triggered
2018-10-04 21:11:51.421 [ome.event.ItemCommandEvent] - Item 'Light_gc_Office_Deckenfluter' received command 40
2018-10-04 21:11:51.443 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 20 to 40
2018-10-04 21:11:51.546 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 40 to 20
2018-10-04 21:12:01.709 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 20 to 40
2018-10-04 21:12:06.707 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41a7a:press triggered
2018-10-04 21:12:06.742 [ome.event.ItemCommandEvent] - Item 'Light_gc_Office_Deckenfluter' received command 60
2018-10-04 21:12:06.756 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 40 to 60
2018-10-04 21:12:16.677 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41a7a:press triggered
2018-10-04 21:12:16.710 [ome.event.ItemCommandEvent] - Item 'Light_gc_Office_Deckenfluter' received command 80
2018-10-04 21:12:16.725 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 60 to 80
2018-10-04 21:12:26.188 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41a7a:press triggered
2018-10-04 21:12:26.222 [ome.event.ItemCommandEvent] - Item 'Light_gc_Office_Deckenfluter' received command 100
2018-10-04 21:12:26.239 [vent.ItemStateChangedEvent] - Light_gc_Office_Deckenfluter changed from 80 to 100
2018-10-04 21:12:36.497 [vent.ChannelTriggeredEvent] - amazondashbutton:dashbutton:deb41a7a:press triggered
2018-10-04 21:12:36.525 [WARN ] [rthome.model.script.actions.BusEvent] - Cannot convert '120' to a command type which item 'Light_gc_Office_Deckenfluter' accepts: [PercentType, OnOffType, IncreaseDecreaseType, RefreshType]
then
    // Assume current dim is 0 if Item is NULL, light will turn on at 20%
    val curr = if(Light_gc_Office_Deckenfluter.state === NULL) 0 else Light_gc_Office_Deckenfluter.state as Number

    val newDim = curr + 20 // calculate the new dim value
    val newState = if(curr <= 100) ON else OFF // determine if the light should be ON or OFF

    if(newState == OFF) Light_gc_Office_Deckenfluter.sendCommand(OFF)
    else Light_gc_Office_Deckenfluter.sendCommand(newDim)
end

Please use code fences for logs as well as code.

As I asked, please add logging to see what newState and newDim are before the call to sendCommand.

how can I activate the logging?


I don´t know exactly what I have to do - sorry.
I have add the following line

logDebug("DashButton.rule", "DashButtonDesperados Debug")
2018-10-04 21:41:43.353 [WARN ] [rthome.model.script.actions.BusEvent] - Cannot convert '120' to a command type which item 'Light_gc_Office_Deckenfluter' accepts: [PercentType, OnOffType, IncreaseDecreaseType, RefreshType].

If you want to see what the value of a variable is, you must include that variable in the log statement

logInfo("DashButton.rule", "newDim = " + newDim + " newState = " + newState)

Look at the examples in the second link above.