My Garadget + MQTT + OpenHAB Setup

Most of the bugs that have been fixed in the MQTT binding were fixed before 2.5 M1. There are very few new bugs that have or will be fixed for 2.5 M2. So don’t wait. Upgrade to 2.5 M1 now.

My main problem with 2.5M1 is the mqtt actions don’t work reliably in code / rules. This is why I had to resort to creating a dummy item in order to publish mqtt messages.

I have a Garadget in a box waiting for me to set up the same stuff. Your configuration will save me a bunch of time! Thanks for sharing it!

I hope the MQTT implementation is not as worrying as it sounded.

Thanks. I am warming up to to idea of just using the particle cloud (which is what garadget uses) rather than trying to do local via MQTT. I did upgrade and the binding in 2.5 M1 works great when using the cloud. Given the widespread use of particle, I am a bit less worried about connectivity issues. I am especially concerned because security-wise, it is my garage door!

I wanted to share some observations about the Garadget Binding and help others who use this device.

The items file example described in the binding https://www.openhab.org/addons/bindings/garadget1/ is a great starting point.

Since I have two doors instead of one, I used that to get it to work for two doors:

garadget.items

Group Garadget
Group UI

String door1               "Garage Door [%s]"              <rollershutter> (Garadget,UI) { garadget="<[330028001447373033383530#door1]" }

// A Contact item supports open and closed, but a Garadget doorStatus_status can be: 
// closed, open, closing, opening, stopped
// (as documented here: https://github.com/Garadget/firmware#door-states-status)
String doorStatus_status_door1  "Door Status [%s]"                      <garagedoor> (Garadget,doorStatus,UI) { garadget="<[330028001447373033383530#doorStatus_status]" }
String doorStatus_time_door1    "Last Change [%s ago]"                  <clock> (Garadget,doorStatus,UI) { garadget="<[330028001447373033383530#doorStatus_time]" }
// Send the doorState item commands like ZERO, HUNDRED, UP, DOWN, ON, OFF, STOP, or "open", "closed" or "stop".
Rollershutter doorState_door1   "Control"                       <rollershutter> (Garadget,UI) { garadget=">[330028001447373033383530#setState],<[330028001447373033383530#doorStatus_status]" }
Number doorStatus_sensor_door1  "Reflection [%d %%]"                      <sun> (Garadget,doorStatus,UI) { garadget="<[330028001447373033383530#doorStatus_sensor]" }
Number doorConfig_srt_door1     "Threshold [%d %%]"                   <battery> (Garadget,doorStatus,UI) { garadget="<[330028001447373033383530#doorConfig_srt]" }
Number doorStatus_signal_door1  "Signal [%d dB]"                      <battery> (Garadget,doorStatus,UI) { garadget="<[330028001447373033383530#doorStatus_signal]" }
String last_app_door1           "Last App [%s]"                                 (Garadget) { garadget="<[330028001447373033383530#last_app]" }
String last_ip_address_door1    "Last IP Address [%s]"                          (Garadget) { garadget="<[330028001447373033383530#last_ip_address]" }
DateTime last_heard_door1       "Last Heard [%1$tm/%1$td %1$tH:%1$tM]"  <clock> (Garadget) { garadget="<[330028001447373033383530#last_heard]" }
Number product_id_door1         "Product ID [%d]"                               (Garadget) { garadget="<[330028001447373033383530#product_id]" }
Switch connected_door1          "Connected [%s]"                                (Garadget) { garadget="<[330028001447373033383530#connected]" }

String doorStatus_door1         "Door Status [%s]"                              (Garadget) { garadget="<[330028001447373033383530#doorStatus]" }

String doorConfig_door1         "Door Config [%s]"                              (Garadget) { garadget="<[330028001447373033383530#doorConfig]" }
Number doorConfig_ver_door1     "Version [%.1f]"                                (Garadget) { garadget="<[330028001447373033383530#doorConfig_ver]" }
Number doorConfig_rdt_door1     "Sensor Scan Interval [%d ms]"                  (Garadget) { garadget="<[330028001447373033383530#doorConfig_rdt]" }
Number doorConfig_mtt_door1     "Door Moving Time [%d ms]"                      (Garadget) { garadget="<[330028001447373033383530#doorConfig_mtt]" }
Number doorConfig_rlt_door1     "Button Press Time [%d ms]"                     (Garadget) { garadget="<[330028001447373033383530#doorConfig_rlt]" }
Number doorConfig_rlp_door1     "Delay betw Presses [%d ms]"                    (Garadget) { garadget="<[330028001447373033383530#doorConfig_rlp]" }
Number doorConfig_srr_door1     "Sensor reads [%d]"                             (Garadget) { garadget="<[330028001447373033383530#doorConfig_srr]" }
Number doorConfig_aot_door1     "Open Timeout Alert [%d min]"                   (Garadget) { garadget="<[330028001447373033383530#doorConfig_aot]" }
Number doorConfig_ans_door1     "Night time alert start [%d min from midnight]" (Garadget) { garadget="<[330028001447373033383530#doorConfig_ans]" }
Number doorConfig_ane_door1     "Night time alert end [%d min from midnight]"   (Garadget) { garadget="<[330028001447373033383530#doorConfig_ane]" }

String netConfig_door1          "Net Config [%s]"                               (Garadget) { garadget="<[330028001447373033383530#netConfig]" }
String netConfig_ip_door1       "IP Address [%s]"                               (Garadget) { garadget="<[330028001447373033383530#netConfig_ip]" }
String netConfig_snet_door1     "Subnet [%s]"                                   (Garadget) { garadget="<[330028001447373033383530#netConfig_snet]" }
String netConfig_gway_door1     "Gateway [%s]"                                  (Garadget) { garadget="<[330028001447373033383530#netConfig_gway]" }
String netConfig_mac_door1      "MAC address [%s]"                              (Garadget) { garadget="<[330028001447373033383530#netConfig_mac]" }
String netConfig_ssid_door1     "SSID [%s]"                                     (Garadget) { garadget="<[330028001447373033383530#netConfig_ssid]" }

// send the setConfig item commands like "ans=1200|ane=420" to set night time alert to 8pm-7am, for example.
String setConfig_door1          "Garage Door 1 Config [%s]"              (Garadget) { garadget=">[330028001447373033383530#setConfig],<[330028001447373033383530#doorConfig]" }

Switch doorStatus_status_door1_timer_10 (doorStatusTimers){expire="10m,command=OFF"}
Switch doorStatus_status_door1_timer_30 (doorStatusTimers){expire="20m,command=OFF"}

String door2               "Garage Door [%s]"              <rollershutter> (Garadget,doorStatus,UI) { garadget="<[2b0027001447373033383530#door2]" }

// A Contact item supports open and closed, but a Garadget doorStatus_status can be: 
// closed, open, closing, opening, stopped
// (as documented here: https://github.com/Garadget/firmware#door-states-status)
String doorStatus_status_door2  "Door Status [%s]"                      <garagedoor> (Garadget,doorStatus,UI) { garadget="<[2b0027001447373033383530#doorStatus_status]" }
String doorStatus_time_door2    "Last Change [%s ago]"                  <clock> (Garadget,doorStatus,UI) { garadget="<[2b0027001447373033383530#doorStatus_time]" }
// Send the doorState item commands like ZERO, HUNDRED, UP, DOWN, ON, OFF, STOP, or "open", "closed" or "stop".
Rollershutter doorState_door2   "Control"                       <rollershutter> (Garadget,doorStatus,UI) { garadget=">[2b0027001447373033383530#setState],<[2b0027001447373033383530#doorStatus_status]" }
Number doorStatus_sensor_door2  "Reflection [%d %%]"                      <sun> (Garadget,doorStatus,UI) { garadget="<[2b0027001447373033383530#doorStatus_sensor]" }
Number doorConfig_srt_door2     "Threshold [%d %%]"                   <battery> (Garadget,doorStatus,UI) { garadget="<[2b0027001447373033383530#doorConfig_srt]" }
Number doorStatus_signal_door2  "Signal [%d dB]"                      <battery> (Garadget,doorStatus,UI) { garadget="<[2b0027001447373033383530#doorStatus_signal]" }
String last_app_door2          "Last App [%s]"                                 (Garadget) { garadget="<[2b0027001447373033383530#last_app]" }
String last_ip_address_door2    "Last IP Address [%s]"                          (Garadget) { garadget="<[2b0027001447373033383530#last_ip_address]" }
DateTime last_heard_door2       "Last Heard [%1$tm/%1$td %1$tH:%1$tM]"  <clock> (Garadget) { garadget="<[2b0027001447373033383530#last_heard]" }
Number product_id_door2         "Product ID [%d]"                               (Garadget) { garadget="<[2b0027001447373033383530#product_id]" }
Switch connected_door2          "Connected [%s]"                                (Garadget) { garadget="<[2b0027001447373033383530#connected]" }

String doorStatus_door2         "Door Status [%s]"                              (Garadget) { garadget="<[2b0027001447373033383530#doorStatus]" }

String doorConfig_door2         "Door Config [%s]"                              (Garadget) { garadget="<[2b0027001447373033383530#doorConfig]" }
Number doorConfig_ver_door2     "Version [%.1f]"                                (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_ver]" }
Number doorConfig_rdt_door2     "Sensor Scan Interval [%d ms]"                  (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_rdt]" }
Number doorConfig_mtt_door2     "Door Moving Time [%d ms]"                      (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_mtt]" }
Number doorConfig_rlt_door2     "Button Press Time [%d ms]"                     (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_rlt]" }
Number doorConfig_rlp_door2     "Delay betw Presses [%d ms]"                    (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_rlp]" }
Number doorConfig_srr_door2     "Sensor reads [%d]"                             (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_srr]" }
Number doorConfig_aot_door2     "Open Timeout Alert [%d min]"                   (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_aot]" }
Number doorConfig_ans_door2     "Night time alert start [%d min from midnight]" (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_ans]" }
Number doorConfig_ane_door2     "Night time alert end [%d min from midnight]"   (Garadget) { garadget="<[2b0027001447373033383530#doorConfig_ane]" }

String netConfig_door2          "Net Config [%s]"                               (Garadget) { garadget="<[2b0027001447373033383530#netConfig]" }
String netConfig_ip_door2       "IP Address [%s]"                               (Garadget) { garadget="<[2b0027001447373033383530#netConfig_ip]" }
String netConfig_snet_door2     "Subnet [%s]"                                   (Garadget) { garadget="<[2b0027001447373033383530#netConfig_snet]" }
String netConfig_gway_door2     "Gateway [%s]"                                  (Garadget) { garadget="<[2b0027001447373033383530#netConfig_gway]" }
String netConfig_mac_door2      "MAC address [%s]"                              (Garadget) { garadget="<[2b0027001447373033383530#netConfig_mac]" }
String netConfig_ssid_door2     "SSID [%s]"                                     (Garadget) { garadget="<[2b0027001447373033383530#netConfig_ssid]" }

// send the setConfig item commands like "ans=1200|ane=420" to set night time alert to 8pm-7am, for example.
String setConfig_door2          "Garage Door 2 Config [%s]"              (Garadget) { garadget=">[2b0027001447373033383530#setConfig],<[2b0027001447373033383530#doorConfig]" }

Switch doorStatus_status_door2_timer_10 (doorStatusTimers){expire="10m,command=OFF"}
Switch doorStatus_status_door2_timer_30 (doorStatusTimers){expire="20m,command=OFF"}

The door status reported by the binding can vary depending on how you open the door and I think also the brand of opener.

It will go one of two ways:

  1. from opening to open, or
  2. from closed to open.

Someone more experienced can probably write this more efficiently, but the below rules worked for me in both conditions.

For my example, I will send an email (specifically, an email-to-SMS text for Verizon) if either door is still open after 10 minutes, and send another notice if still open after 30 minutes.

Even though the Garadget app lets you assign one email address, it does not let you assign more than one (or a different one to each door), nor does it allow you to use a email-to-SMS text address (at least it did not accept mine).

So, by using this rule format with the email action, the binding will let you send notification to as many addresses or phone numbers you wish. Note that using this method is a bit slower than if you use Pushover and/or the Gardget app notification. In my case, the difference was less than one minute longer. So, if you are going for 10 minutes, you might want to set your timer for 9 minutes instead.

garadget.rules

var Timer door1OpenTimer = null
var Timer door2OpenTimer = null
val int timeoutMinutes = 30

rule "Garage Door 1 Left Open for 10 minutes first"
when
    Item doorStatus_status_door1 changed from closed to open
then
    if (door1OpenTimer === null) {
        door1OpenTimer = createTimer(now.plusMinutes(10)) [|
        door1OpenTimer = null
        if (doorStatus_status_door1.state.toString =="open"){
            logInfo("Garage Door 1", "The garage door for the Camry has been left open for 10 minutes. Message texted to owner.")
            sendMail("5127775689@vtext.com", " ", "The garage door for the Camry has been left open for 10 minutes.")
            }
        ]
    }
end

rule "Garage Door 1 Left Open for 10 minutes second"
when
    Item doorStatus_status_door1 changed from opening to open
then
    if (door1OpenTimer === null) {
        door1OpenTimer = createTimer(now.plusMinutes(10)) [|
        door1OpenTimer = null
        if (doorStatus_status_door1.state.toString =="open"){
            logInfo("Garage Door 1", "The garage door for the Camry has been left open for 10 minutes. Message texted to owner.")
            sendMail("5127775689@vtext.com", " ", "The garage door for the Camry has been left open for 10 minutes.")
            }
        ]
    }
end

rule "Garage Door 1 Left Open for 30 minutes first"
when
    Item doorStatus_status_door1 changed from closed to open
then
    if (door1OpenTimer === null) {
        door1OpenTimer = createTimer(now.plusMinutes(timeoutMinutes)) [|
        door1OpenTimer = null
        if (doorStatus_status_door1.state.toString =="open"){
            logInfo("Garage Door 1", "The garage door for the Camry has been left open for 30 minutes. Message texted to owner.")
            sendMail("5127775689@vtext.com", " ", "The garage door for the Camry has been left open for 30 minutes.")
            }
        ]
    }
end

rule "Garage Door 1 Left Open for 30 minutes second"
when
    Item doorStatus_status_door1 changed from opening to open
then
    if (door1OpenTimer === null) {
        door1OpenTimer = createTimer(now.plusMinutes(timeoutMinutes)) [|
        door1OpenTimer = null
        if (doorStatus_status_door1.state.toString =="open"){
            logInfo("Garage Door 1", "The garage door for the Camry has been left open for 30 minutes. Message texted to owner.")
            sendMail("5127775689@vtext.com", " ", "The garage door for the Camry has been left open for 30 minutes.")
            }
        ]
    }
end

rule "Garage Door 2 Left Open for 10 minutes first"
when
    Item doorStatus_status_door2 changed from closed to open
then
    if (door2OpenTimer === null) {
        door2OpenTimer = createTimer(now.plusMinutes(10)) [|
        door2OpenTimer = null
        if (doorStatus_status_door2.state.toString =="open"){
            logInfo("Garage Door 2", "The garage door for the Corolla has been left open for 10 minutes. Message texted to owner.")
            sendMail("5125550123@vtext.com", " ", "The garage door for the Corolla has been left open for 10 minutes.")
            }
        ]
    }
end

rule "Garage Door 2 Left Open for 10 minutes second"
when
    Item doorStatus_status_door2 changed from opening to open
then
    if (door2OpenTimer === null) {
        door2OpenTimer = createTimer(now.plusMinutes(10)) [|
        door2OpenTimer = null
        if (doorStatus_status_door2.state.toString =="open"){
            logInfo("Garage Door 2", "The garage door for the Corolla has been left open for 10 minutes. Message texted to owner.")
            sendMail("5125550123@vtext.com", " ", "The garage door for the Corolla has been left open for 10 minutes.")
            }
        ]
    }
end

rule "Garage Door 2 Left Open for 30 minutes first"
when
    Item doorStatus_status_door2 changed from closed to open
then
    if (door2OpenTimer === null) {
        door2OpenTimer = createTimer(now.plusMinutes(timeoutMinutes)) [|
        door2OpenTimer = null
        if (doorStatus_status_door2.state.toString =="open"){
            logInfo("Garage Door 2", "The garage door for the Corolla has been left open for 30 minutes. Message texted to owner.")
            sendMail("5125550123@vtext.com", " ", "The garage door for the Corolla has been left open for 30 minutes.")
            }
        ]
    }
end

rule "Garage Door 2 Left Open for 30 minutes second"
when
    Item doorStatus_status_door2 changed from opening to open
then
    if (door2OpenTimer === null) {
        door2OpenTimer = createTimer(now.plusMinutes(timeoutMinutes)) [|
        door2OpenTimer = null
        if (doorStatus_status_door2.state.toString =="open"){
            logInfo("Garage Door 2", "The garage door for the Corolla has been left open for 30 minutes. Message texted to owner.")
            sendMail("5125550123@vtext.com", " ", "The garage door for the Corolla has been left open for 30 minutes.")
            }
        ]
    }
end

Being new to openHAB, I welcome any improvements you can offer to this sample code!

This should be implementable in a single Rule.

  • You can have more than one trigger for a Rule
  • You are not required to have a from in the trigger
  • In this case, we can handle the open or closed in the same Rule
  • Inside the Rule, you can figure out which Item triggered the Rule using triggeringItem
  • Design Pattern: Expire Binding Based Timers make for simpler code so I prefer to use them. Then we can also use Design Pattern: Associated Items, but I’ll show it using regular Timers because it will be easier to handle the two different times that way.
  • This would be a perfect use for Design Pattern: Looping Timers
  • Put your Items into a Group and you can simplify the triggers. Then you can add all the rest of your doors and windows to that Group and reuse this same Rule for reminders on those.
import org.eclipse.smarthome.model.script.ScriptServiceUtil
import java.util.Map

val Map<String, Timers> timers = newHashMap

rule "A garage door opened"
when
    Member of doorStatus changed to open or
    Member of doorStatus changed to closed
then
    // Get the timer
    val timer = timers.get(triggeringItem.name)

    // A door changed to ON and there isn't already a timer (shouldn't happen where timer !== null)
    if(triggerinItem.state.toString == "open" && timer === null) {
        var time = 10 // we use this as a flag to see if this is the first or second time the timer runs

        timers.put(createTimer(now.plusMinutes(time.intValue), [ |

            // The Timer would have been canceled by now if the door closed, no need to check

            // Get the car name and email address assocaited with triggeringItem
            val carName = transform("MAP", "garagedoors.map", triggeringItem.name) // see https://community.openhab.org/t/design-pattern-human-readable-names-in-messages/34117
            val phone = transform("MAP", "phones.map", triggeringItem.name)

            // Send the alert
            logInfo("Garage Door 1", "The garage door for the " + carName + " has been left open for " + time + " minutes. Message texted to owner.")
            sendmail(phone, " ", "The garage door for the " + carName + " has been left open for " + time + " minutes")

            // if time == 10, this is the first time we ran, change time to 30 so next time we know it's the second
            // time the timer was run and we can send the appropriate alert
            if(time == 10) { 
                time = 30
                timers.get(triggeringItem.name).reschedule(now.plusMinutes(time.intValue))
            }
            else {
                timers.put(triggeringItem.name, null) // we are done after the second alert
            }

        ])
    } // changed to open
    else {
        timers.get(triggeringItem.name)?.cancel // the ? means only call cancel if the get is not null
        timers.put(triggeringItem.name, null)
    } // changed to closed
end

This is better. But what if we used Expire based Timers after all?

import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "A garage door changed to open or closed"
when
    Member of doorStatus changed to open or
    Member of doorStatus changed to closed
then
    val timer1 = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_timer_10")
    val timer2 = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_timer_30")
    
    if(triggeringItem.state.toString == "closed") {
        timer1.postUpdate(OFF)
        tiemr2.postUpdate(OFF)
    }

    else if(triggeringItem.state.toString == "open" && timer1 == OFF && timer2 == OFF) {
        timer1.sendCommand(ON)
    }
    // othewise ignore the event, or log a warning as this should never happen
end

rule "A timer expired"
when
    Member of doorStatusTimers received command ON or
    Member of doorStatusTimers received command OFF
then
    // Get the appropriate information for this timer
    val carName = transform("MAP", "garagedoors.map", triggeringItem.name) 
    val phone = transform("MAP", "phones.map", triggeringItem.name)
    val time = triggeringItem.name.split("_").get(4)

    // Send the alert
    logInfo("Garage Door", "The garage door for the " + carName + " has been left open for " + time + " minutes. Message texted to owner.")
    sendmail(phone, " ", "The garage door for the " + carName + " has been left open for " + time + " minutes")

    // Schedule the next timer
    if(time == "10") {
        sendCommand(triggeringItem.name.replace("10", "30"))
    }
end    

It’s fewer lines of code and it is significantly simpler.

If you are willing to fudge on your requirements, the Rules can be made even simpler. For example, if you just want an alert every 10 minute or every 30 minutes until it closes, I can drop the lines of code in this last example by half.

For more ways to avoid duplicated code see Design Pattern: DRY, How Not to Repeat Yourself in Rules DSL.

1 Like

Wow, thanks Rich! I will need to study all you have done here and it is clear I have much to learn. I appreciate your thoughtful advice!

If the two timers are set by door1 and then door2 is opened five minutes later, it appears that the timer1 and timer2 values would be overwritten by door2, causing the door1 timers to be lost, no?

No because each door has it’s own Timer. In the first example, we put that timer into a Map keyed on the name of the door’s Item. That’s what a Map does, it stores a bunch of values with a corresponding key.

In the Expire example, each door has it’s own Timer Item and it is names with the same name as the door with “_timer_10” or “_timer_30”.

In short, each door has it’s own timers.

Thanks Rich. I updated garadget.items in my first post to add the group names doorStatus and doorStatusTimers, and added the timer items.

Here is what the ‘near final’ version looks like, but the last rule: “A timer expired” does not yet work for two reasons, described below.

garadget.rules

import org.eclipse.smarthome.model.script.ScriptServiceUtil

rule "A garage door changed to open or closed"
when
    Member of doorStatus changed to open or
    Member of doorStatus changed to closed 
then
    val carName = transform("MAP", "garagedoors.map", triggeringItem.name)
    logDebug("Garage Door", "The garage door status for " + carName + " changed. Starting timer.")
    val timer1 = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_timer_10")
    val timer2 = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_timer_30")
    
   // If system is restarted, need to check for null state and if needed change default to OFF \n
   if( timer1.state === NULL){
      timer1.postUpdate(OFF)
      }
   if( timer2.state === NULL){
      timer2.postUpdate(OFF)
      }
    
    // If one of the doors is closed or gets closed, turn off its timer \n  
    if(triggeringItem.state.toString == "closed") {
        timer1.postUpdate(OFF)
        timer2.postUpdate(OFF)
        logDebug("Garage Door", "The garage door for " + carName + " is (or has just been) closed. Timer is canceled.")
    }    
    logDebug("Garage Door", "The trigger is " + triggeringItem.state.toString + ".")
    
    // Check the state of both timers when the door's state is open. If neither timer is on, turned on timer1 \n
    if(triggeringItem.state.toString == "open" && timer1.state == OFF && timer2.state == OFF){
        logDebug("Garage Door", "Trigger to open timer received for " + carName + ". Next, the timer command should work.")
        sendCommand(timer1, ON)
        logInfo("Garage Door", "The garage door for " + carName + " was opened and timer1 is not already on. Starting timer1.")
    }  
logDebug("Garage Door", "The value for timer1 is " + timer1 + " and timer2 is " + timer2 +". The trigger is " + triggeringItem.state.toString + ".")

// otherwise ignore the event, or log a warning as this should never happen\n
end

rule "A timer expired"
when 
    Member of doorStatusTimers received command ON or
    Member of doorStatusTimers received command OFF
then
    // Get the appropriate information for this timer\n
    val carName = transform("MAP", "garagedoors.map", triggeringItem.name) 
    val phone = transform("MAP", "phones.map", triggeringItem.name)
    val time = triggeringItem.name.split("_").get(4)
    // Send the alert\n
    logInfo("Garage Door", "val time is " + time + " , and the garage door for the " + carName + " has been left open for " + time + " minutes. Message texted to owner.")
    sendMail(phone, " ", "The garage door for the " + carName + " has been left open for " + time + " minutes")
    // Schedule the next timer\n
    if(time == "10") {
        sendCommand(triggeringItem.name, triggeringItem.name.replace("10", "30"))
        logInfo("Garage Door", "The garage door has not been been closed after ten minutes, so timer is now set for 20 more minutes.")
        }
end  

The first issue:
When a door is opened, timer1 is created, it triggers the rule “A timer expired” (because command ON is received), which sends an email alert - not when the timer expires - but when the timer gets created. I think in part because we have:

val time = triggeringItem.name.split("_").get(4)

When the first timer is created, val time == “10”.

The second issue is this if statement from the same rule, using val time based on the item name, not the expiration of the timer:

    if(time == "10") {
        sendCommand(triggeringItem.name.replace("10", "30"))
        logInfo("Garage Door", "The garage door has not been been closed after ten minutes, so timer is now set for 20 more minutes.")
        }

the sendCommand statement generated this error:

Rule 'A timer expired': An error occurred during the script execution: index=1, size=1

So I changed it from

sendCommand(triggeringItem.name.replace("10", "30"))

to:

sendCommand(triggeringItem.name, triggeringItem.name.replace("10", "30"))

This eliminated the error, but I am getting this bus event error:

Cannot convert 'doorStatus_status_door1_timer_30' to a command type which item 'doorStatus_status_door1_timer_10' accepts: [OnOffType, RefreshType].

Also, even though I have this in my items file:

Switch doorStatus_status_door1_timer_10 (doorStatusTimers){expire="10m,command=OFF"}

The timer does not ever expire. I am working on fixes for all of this. Any advice?

garagedoors.map

doorStatus_status_door1=Camry
doorStatus_status_door2=Corolla
doorStatus_status_door1_timer_10=Camry
doorStatus_status_door1_timer_30=Camry
doorStatus_status_door2_timer_10=Corolla
doorStatus_status_door2_timer_30=Corolla

garadget.map

open=UP
close=DOWN
stop=STOP
ON=open
UP=open
0=open
OFF=close
DOWN=close
100=close
STOP=stopped

phones.map

doorStatus_status_door1= 5127775689@vtext.com
doorStatus_status_door2=5125550123@vtext.com
doorStatus_status_door1_timer_10=5127775689@vtext.com
doorStatus_status_door1_timer_30=5127775689@vtext.com
doorStatus_status_door2_timer_10=5125550123@vtext.com
doorStatus_status_door2_timer_30=5125550123@vtext.com

The timer expired Rule should only ever be triggered on received command OFF. That was a dumb mistake in my original version. Sorry about that.

Another oops. The command isn’t actually being sent. sendCommand needs two arguments, the name of the Item and the actual command. That line is only providing the name of the Item. I think

sendCommand(triggeringItem.name.replace("10", "30"), "ON")

is correct.

You are getting an error because you are trying to send the command “doorStatus_status_door1_timer_30” to a Switch Item. Obviously, that is a nonsense command to send to a Switch, which can only accept “ON” or “OFF” as a command.

Thanks Rich, it works correctly with those changes. I appreciate your help very much. It is encouraging and helps to keep an old man like me learning!

Here is the final solution.

garadget.rules

import org.eclipse.smarthome.model.script.ScriptServiceUtil

// Besides the import ScriptServiceUtil, these rules require the Garadget and Expire Bindings, and the Javascript and Map Transformations \n
// Script can be used to send specifically-timed, multiple email or text notifications, and tailored based on how long a garage door has been \n
// left open. This script is for operating two garage doors. \n

rule "A garage door changed to open or closed" 
when
    Member of doorStatus changed to open or
    Member of doorStatus changed to closed 
then
    val carName = transform("MAP", "garagedoors.map", triggeringItem.name)
    logDebug("Garage Door", "The garage door status for " + carName + " changed. Starting timer.")
    val timer1 = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_timer_10")
    val timer2 = ScriptServiceUtil.getItemRegistry.getItem(triggeringItem.name+"_timer_30")
    
   // If system is restarted, need to check for null state and if needed change default to OFF \n
   if( timer1.state === NULL){
      timer1.postUpdate(OFF)
      }
   if( timer2.state === NULL){
      timer2.postUpdate(OFF)
      }
    
    // If one of the doors is closed or gets closed, turn off its timer \n  
    if(triggeringItem.state.toString == "closed") {
        timer1.postUpdate(OFF)
        timer2.postUpdate(OFF)
        logDebug("Garage Door", "The garage door for " + carName + " is (or has just been) closed. Timer is canceled.")
    }    
    logDebug("Garage Door", "The trigger is " + triggeringItem.state.toString + ".")
    
    // Check the state of both timers when the door's state is open. If neither timer is on, turn on timer1 \n
    if(triggeringItem.state.toString == "open" && timer1.state == OFF && timer2.state == OFF){
        logDebug("Garage Door", "Trigger to open timer received for " + carName + ". Next, the timer command should work.")
        sendCommand(timer1, ON)
        logInfo("Garage Door", "The garage door for " + carName + " was opened and timer1 is not already on. Starting timer1.")
    }  
    logDebug("Garage Door", "The value for timer1 is " + timer1 + " and timer2 is " + timer2 +". The trigger is " + triggeringItem.state.toString + ".")

// otherwise ignore the event, or log a warning as this should never happen\n
end

rule "A timer expired"
when   
    Member of doorStatusTimers received command OFF
then
    // Get the appropriate information for this timer\n
    val carName = transform("MAP", "garagedoors.map", triggeringItem.name) 
    val phone = transform("MAP", "phones.map", triggeringItem.name)
    val time = triggeringItem.name.split("_").get(4)
    // Send the alert\n
    logDebug("Garage Door", "val time is " + time + ", and the garage door for the " + carName + " has been left open for " + time + " minutes. Message texted to owner.")
    sendMail(phone, " ", "The garage door for the " + carName + " has been left open for " + time + " minutes")
    // Schedule the next timer\n
    if(time == "10") {
        sendCommand(triggeringItem.name.replace("10", "30"), "ON")
        logInfo("Garage Door", "The garage door has not been been closed after ten minutes, so timer is now set for 20 more minutes.")
        }
end

Thanks to the release of 2.5M2, my garadget setup is now much simpler. I have updated the original post above.

I have an issue, the states are not updated

bridge:

mqtt:broker:mosquitto (Type=Bridge, Status=ONLINE, Label=MQTT Broker, Bridge=null)

things:

Thing mqtt:topic:mosquitto:garage "Garage Door" (mqtt:broker:mosquitto) @ "Garage" {
    Channels:
        Type rollershutter : control "Control"		[ 
            stateTopic="garadget/garage/status", 
            transformationPattern="JSONPATH:$.status∩MAP:garadget.map", 
            commandTopic="garadget/garage/command",
            transformationPatternOut="MAP:garadget.map"
            ]
        Type string : time "Time"                   [ stateTopic="garadget/garage/status", transformationPattern="JSONPATH:$.time" ]
        Type string : status "Status"				[ stateTopic="garadget/garage/status", transformationPattern="JSONPATH:$.status" ]
        Type number : reflection "Reflection"		[ stateTopic="garadget/garage/status", transformationPattern="JSONPATH:$.sensor" ]
        Type number : rssi "Wifi Signal Strength"	[ stateTopic="garadget/garage/status", transformationPattern="JSONPATH:$.signal" ]
        Type number : brightness "Light Brightness"	[ stateTopic="garadget/garage/status", transformationPattern="JSONPATH:$.bright" ]
        Type number : mtt "Door Moving Time"        [ stateTopic="garadget/garage/config", transformationPattern="JSONPATH:$.mtt" ]
}

items:

Rollershutter	Garage_Door "Garage Door"		<rollershutter> (gPersist)	["Blinds"]	{ autoupdate="false", channel="mqtt:topic:mosquitto:garage:control" }
String			Garage_Time "Time"														{ channel="mqtt:topic:mosquitto:garage:time" }
String			Garage_Status "Status"			<garagedoor>	(gPersist)				{ channel="mqtt:topic:mosquitto:garage:status" }
Number			Garage_RSSI "RSSI [%d dBm]"		<wifi>									{ channel="mqtt:topic:mosquitto:garage:rssi" }
Number			Garage_Reflection "Reflection Level [%d]" 								{ channel="mqtt:topic:mosquitto:garage:reflection" }
Number			Garage_Brightness "Brightness Level [%d]"								{ channel="mqtt:topic:mosquitto:garage:brightness" }
Number			Garage_MovingTime "Door Moving Time [%d ms]"							{ channel="mqtt:topic:mosquitto:garage:mtt" }

garadget.map:

closed=100
close=100
closing=50
open=0
stopped=stop
stop=STOP
opening=50
0=open
100=close
STOP=stop

in mosquitto log is see, garadget is connecting:

1585928854: mosquitto version 1.6.8 starting
1585928854: Using default config.
1585928854: Opening ipv4 listen socket on port 1883.
1585928854: Opening ipv6 listen socket on port 1883.
1585928860: New connection from 127.0.0.1 on port 1883.
1585928860: New client connected from 127.0.0.1 as 1c4f52b1-ccdd-4e3e-b366-0683051246e2 (p2, c1, k60).
1585928860: No will message specified.
1585928860: Sending CONNACK to 1c4f52b1-ccdd-4e3e-b366-0683051246e2 (0, 0)
1585928860: Received SUBSCRIBE from 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: homeassistant/# (QoS 1)
1585928860: 1c4f52b1-ccdd-4e3e-b366-0683051246e2 1 homeassistant/#
1585928860: Sending SUBACK to 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: Received SUBSCRIBE from 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: garadget/garage/config (QoS 1)
1585928860: 1c4f52b1-ccdd-4e3e-b366-0683051246e2 1 garadget/garage/config
1585928860: Sending SUBACK to 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: Received SUBSCRIBE from 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: garadget/garage/status (QoS 1)
1585928860: 1c4f52b1-ccdd-4e3e-b366-0683051246e2 1 garadget/garage/status
1585928860: Sending SUBACK to 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: Received SUBSCRIBE from 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928860: +/+/$homie (QoS 1)
1585928860: 1c4f52b1-ccdd-4e3e-b366-0683051246e2 1 +/+/$homie
1585928860: Sending SUBACK to 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928872: New connection from 10.0.1.42 on port 1883.
1585928872: New client connected from 10.0.1.42 as Garage (p1, c1, k30).
1585928872: No will message specified.
1585928872: Sending CONNACK to Garage (0, 0)
1585928872: Received SUBSCRIBE from Garage
1585928872: garadget/Garage/command (QoS 0)
1585928872: Garage 0 garadget/Garage/command
1585928872: Sending SUBACK to Garage
1585928872: Received SUBSCRIBE from Garage
1585928872: garadget/Garage/set-config (QoS 0)
1585928872: Garage 0 garadget/Garage/set-config
1585928872: Sending SUBACK to Garage
1585928872: Received PUBLISH from Garage (d0, q0, r1, m0, 'garadget/Garage/status', ... (69 bytes))
1585928872: Received PUBLISH from Garage (d0, q0, r1, m0, 'garadget/Garage/config', ... (211 bytes))
1585928902: Received PINGREQ from Garage
1585928902: Sending PINGRESP to Garage
1585928920: Received PINGREQ from 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928920: Sending PINGRESP to 1c4f52b1-ccdd-4e3e-b366-0683051246e2
1585928932: Received PINGREQ from Garage
1585928932: Sending PINGRESP to Garage

Any hints ?

Have you looked in your openhab.log and events.log for clues, when the garadget publish its status message?
Have you installed the JSONPATH service?

Hi

JSONPATH is installed via PaperUI -> Add-ons -> Transformations

openhab.log (DEBUG for binding MQTT enabled):

2020-04-03 17:52:05.799 [INFO ] [.reconnect.PeriodicReconnectStrategy] - Try to restore connection to '127.0.0.1'. Next attempt in 60000ms
2020-04-03 17:52:05.802 [INFO ] [.transport.mqtt.MqttBrokerConnection] - Starting MQTT broker connection to '127.0.0.1' with clientid 1c4f52b1-ccdd-4e3e-b366-0683051246e2

events.log:

2020-04-03 17:52:05.864 [hingStatusInfoChangedEvent] - 'mqtt:topic:mosquitto:garage' changed from OFFLINE (BRIDGE_OFFLINE) to ONLINE

That appears to be your openHAB subscribing to a topic

That appears to be your device publishing to a topic.

Topics do not match (character case matters)

1 Like

@rossko57
That’s it. You saved my day, thx a lot.

1 Like

Well, first of all thanks for these useful hints to get garadget working via MQTT.
I am interested in polling the status and get these information. The rule example has been deleted. Being a MQTT beginner, I was not able to figure out how to do that. How the rule look like?

Thanks and happy eastern

EDIT:
Found it with

rule "Poll garadget status via MQTT"
	when
	    Time cron "0 * * * * ?"   // every minute
	then
		val mqttActions = getActions("mqtt","mqtt:systemBroker:embedded-mqtt-broker")
		mqttActions.publishMQTT("garadget/Garagentor/command","get-status")
end

By the way, you can just do

Time cron "0 * * * * ?"

* means every possible value, which in the minute field means every minute. You could also say */1 but that would be redundant.
For every 5 minutes you’d use */5, and so on.