Things Gateway - the Old House & Samsung Buttons


Perhaps I'm being overly effusive, but right now, the Samsung SmartThings Button is my Holy Grail of the Internet of Things.  Coupled with the Things Gateway from Mozilla and my own Web Thing API rule system, the Samsung Button grants me invincibility at solving some vexing Smart Home automation tasks.

Consider this problem: my kitchen is in an old decrepit farm house built in 1920.  The kitchen has a challenging layout with no good space for any modern appliance.  The only wall for the refrigerator is annoyingly narrower than an average refrigerator.  Unfortunately, the only switches for the kitchen and pantry lights are on that wall, too.  The refrigerator blocks the switches to the point they can only be felt, not seen.

For twenty years, I've been fine slipping my hand into the dusty cobwebs behind the refrigerator to turn on the lights.  I can foresee the end of this era.  I'm imagining two Samsung Buttons magnetically tacked to a convenient and accessible side of the refrigerator: one for the pantry and one for the kitchen.

The pantry light is the most common light to be inadvertently left on for hours at a time.  Nobody wants to reach behind the refrigerator to turn off the light.  This gives me an idea.  I'm going to make the new pantry button turn on the light for only 10 minutes at a time.  Rarely is anyone in there for longer than that.  Sometimes, however, it is handy to have it on for longer.  So I'll make each button press add ten minutes to the timer.  Need the light on for 30 minutes?  Press the button three times.  To appease the diligent one in the household that always remembers to turn lights off, a long press to the button turns the light off and cancels the timer:
class PantryLightTimerRule(Rule):

    def register_triggers(self):
        self.delay_timer = DelayTimer(self.config, "adjustable_delay", "10m")
        self.PantryButton.subscribe_to_event('pressed')
        self.PantryButton.subscribe_to_event('longPressed')
        return (self.PantryButton, self.delay_timer, self.PantryLight)

    def action(self, the_triggering_thing, the_trigger_event, new_value):
        if the_triggering_thing is self.PantryButton and the_trigger_event == 'pressed':
            if self.PantryLight.on:
                self.delay_timer.add_time()  # add ten minutes
            else:
                self.PantryLight.on = True

        elif the_triggering_thing is self.PantryButton and the_trigger_event == 'longPressed':
            self.PantryLight.on = False

        elif the_triggering_thing is self.delay_timer:
            self.PantryLight.on = False

        elif the_triggering_thing is self.PantryLight and new_value is False:
            self.delay_timer.cancel()

        elif the_triggering_thing is self.PantryLight and new_value is True:
            self.delay_timer.add_time()  # add ten minutes
(see this code in situ in the timer_light_rule.py file in the pywot rule system demo directory)
Like all rules, there are two parts: registering the things that trigger the rule and the action that the rule takes when triggered.

This rules uses three triggers: the PantryButton (as played by Samsung), a timer called delay_timer, and the PantryLight (as played by an IKEA bulb).  The register_triggers method creates the timer with a default time increment of 10 minutes.  It subscribes to the "pressed" and "longPressed" events that the PantryButton can emit.  It returns a tuple containing these two things coupled with the reference to the PantryLight itself.

How is the PantryLight itself considered to be a trigger?  Since the bulb in the pantry is to be a smart bulb, any other controller in the house could theoretically turn it on.  No matter what turns it on, I want my timer rule to eventually turn it off.  Anytime something turns that light on, my rule will fire.

In the action method, in the last two lines you can see how I exploit the idea that anything turning the light on triggers the timer.  If the_triggering_thing is the PantryLight and it was turned from off to on, this rule will add time to the delay_timer.  If the delay_timer wasn't running, adding time to it will start it.

Further, going back up two more lines, you can see how turning off the light by any means cancels the delay_timer.

Going back up another line, you can see how I handle the timer naturally timing out.  It just turns off the light.  Yeah, that will result in the action method getting a message about the light turning off.  We've already seen that action will try to cancel the timer, but in this case, the timer isn't running anymore, so the cancel is ignored.

Finally, let's consider the top two cases of the action method.  For both, the_triggering_thing is the PantryButton. If the button is longPressed, it turns off the light which in turn will cancel the timer.  If there is a regular short press, the timer gets an additional ten minutes if the light was already on or the light gets turned on if it was off.

This seems to work really well, but it hasn't been in place for more than a few hours...
Now there's the case of the main kitchen light itself.  The room is wired for a single bulb in the middle of the ceiling.  That's worthless for properly lighting the space.  I adapted the bulb socket to be an outlet and then an LED shoplight has just enough cord to reach to over the stove.  There's another over the kitchen counter and a third over the sink.  Each light has a different way to turn it on, all of which are awkward in one way or another.

This will change with the second Samsung button.  It seems we only use the kitchen lights in certain combinations.  Multiple presses to the Samsung button will cycle through these combinations in this order:
  1. all off
  2. stove light only
  3. stove light & counter light
  4. stove light, counter light & sink light
  5. counter light & sink light
  6. sink light only
  7. counter light only
class CombinationLightRule(Rule):

    def initial_state(self):
        self.index = 0
        self.combinations = [
            (False, False, False),
            (True, False, False),
            (True, True, False),
            (True, True, True),
            (False, True, True),
            (False, False, True),
            (False, True, False),
        ]

    def register_triggers(self):
        self.KitchenButton.subscribe_to_event('pressed')
        self.KitchenButton.subscribe_to_event('longPressed')
        return (self.KitchenButton, )

    def set_bulb_state(self):
        self.StoveLight.on = self.combinations[self.index][0]
        self.CounterLight.on = self.combinations[self.index][1]
        self.SinkLight.on = self.combinations[self.index][2]

    def action(self, the_triggering_thing, the_trigger_event, new_value):
        if the_trigger_event == "pressed":
            self.index = (self.index + 1) % len(self.combinations)
            self.set_bulb_state()

        elif the_trigger_event == "longPressed":
            self.index = 0
            self.set_bulb_state()
(see this code in situ in the combination_light_rule.py file in the pywot rule system demo directory)
This is just a simple finite state machine.  The three shop lights are controlled by the list, combinations, referenced by an index.  The only trigger is the KitchenButton (again, played by a Samsung Button).  If the button is short pressed the index is incremented modulo the number of combinations.  If the button is longPressed, the finite state machine is reset to state 0 and all the shop lights turned off.

These two Rules can be found in my pywot github repo.  To learn how to experiment with the Things Gateway with Python, see the Mozilla Project Things and my own post about setting up pywot.

I can think of so many applications for these Samsung Buttons, I may need to acquire a pallet of them...