Sync 2 devices with rules API

G’day all. I need some help writing a rules API script to sync 2 devices. I have a door lock that works through google and I’ve set up a virtual switch that syncs with the door lock status in google. This works well except as the switch is just a switch in smartthings i can’t utilise it in smartthings security. I set up a virtual lock in smartthings to use instead of the switch in google but this doesn’t show up in google. I’ve decided I’ll have to uses a virtual switch to link google to smartthings and vice versa but to get this to work with smartthings security I need to sync the virtual switch to the virtual lock.

anyway, I’ve gotten the virtual switch to virtual lock working one way with a rule stating that:

if virtual switch - on, then virtual lock - locked
else virtual lock - unlocked

using the following code where 892d6707-57db-42a1-afdc-67cd02dc39db is the switch and 3c90112b-adf1-4fb7-91ab-cbffde4de8f7 is the lock

{
    "name": "If front door is switch changes, sync virtual door lock",
    "actions": [
        {
            "if": {
                "equals": {
                    "right": {
                        "device": {
                            "devices": [
                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                            ],
                            "component": "main",
                            "capability": "switch",
                            "attribute": "switch"
                        }
                    },
                    "left": {
                        "string": "on"
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "lock",
                                    "command": "lock"
                                }
                            ]
                        }
                    }
                ],
                "else": [
                    {
                        "command": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "lock",
                                    "command": "unlock",
                                    "arguments": []
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}

this works well but I can’t use the virtual lock to change the virtual switch state (which unfortunately due to security reasons in google I can only use to lock the door). I tried adding another if/then routine (if virtual switch - on, then virtual lock - locked, else virtual lock - unlocked, if virtual switch - off, then virtual lock - unlocked, else virtual lock - locked) under the first routine but that just put the whole thing into a loop. then I tried nesting if statements like this

{
    "name": "If front door is switch changes, sync virtual door lock",
    "actions": [
        {
            "if": {
                "equals": {
                    "right": {
                        "device": {
                            "devices": [
                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                            ],
                            "component": "main",
                            "capability": "switch",
                            "attribute": "switch"
                        }
                    },
                    "left": {
                        "string": "on"
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "lock",
                                    "command": "lock"
                                }
                            ]
                        }
                    }
                ],
                "if": {
                    "equals": {
                        "right": {
                            "device": {
                                "devices": [
                                    "892d6707-57db-42a1-afdc-67cd02dc39db"
                                ],
                                "component": "main",
                                "capability": "switch",
                                "attribute": "switch"
                            }
                        },
                        "left": {
                            "string": "off"
                        }
                    },
                    "then": [
                        {
                            "command": {
                                "devices": [
                                    "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                                ],
                                "commands": [
                                    {
                                        "component": "main",
                                        "capability": "lock",
                                        "command": "unlock"
                                    }
                                ]
                            }
                        }
                    ],
                    "if": {
                        "equals": {
                            "right": {
                                "device": {
                                    "devices": [
                                        "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                                    ],
                                    "component": "main",
                                    "capability": "lock",
                                    "attribute": "lock"
                                }
                            },
                            "left": {
                                "string": "lock"
                            }
                        },
                        "then": [
                            {
                                "command": {
                                    "devices": [
                                        "892d6707-57db-42a1-afdc-67cd02dc39db"
                                    ],
                                    "commands": [
                                        {
                                            "component": "main",
                                            "capability": "switch",
                                            "command": "on"
                                        }
                                    ]
                                }
                            }
                        ],
                        "else": [
                            {
                                "command": {
                                    "devices": [
                                        "892d6707-57db-42a1-afdc-67cd02dc39db"
                                    ],
                                    "commands": [
                                        {
                                            "component": "main",
                                            "capability": "switch",
                                            "command": "off",
                                            "arguments": []
                                        }
                                    ]
                                }
                            }
                        ]
                    }
                }
            }
        }
    ]
}

but that didn’t work and I got the following error

{
    "requestId": "4353716590818799289",
    "error": {
        "code": "ConstraintViolationError",
        "message": "The request is malformed.",
        "details": [
            {
                "code": "BodyMalformedError",
                "target": "if",
                "message": "Unrecognized field \"if\" (class v20190122.internal.st.behaviors.IfAction), not marked as ignorable",
                "details": []
            }
        ]
    }
}

i tried swapping the second and third if statements to elseif but again it didn't like it.

I suppose I could probably put this into 2 rules but I get the feeling that if I did that it would just cause another loop. this should be such a basic rule that it should have an example in the sample rules like google does @nayelyz .

Just a syntax error. You close out your first “if” at the end of the “then” with a “],”, if you add another “}” after the “]” but before the comma that will close out your first “if” action. Then you can add a second and third. You just have to remember to close each statement before starting the next.

Here’s an example of what I mean.

{
    "name": "Upstairs Dehumidifier",
    "timeZoneId": "America/New_York",
    "actions": [
        {
            "if": {
                "and": [
                    {
                        "between": {
                            "value": {
                                "time": {
                                    "reference": "Now"
                                }
                            },
                            "start": {
                                "time": {
                                    "reference": "noon",
                                    "offset": {
                                        "value": {
                                            "integer": -270
                                        },
                                        "unit": "Minute"
                                    }
                                }
                            },
                            "end": {
                                "time": {
                                    "reference": "noon",
                                    "offset": {
                                        "value": {
                                            "integer": 360
                                        },
                                        "unit": "Minute"
                                    }
                                }
                            }
                        }
                    },
                    {
                        "equals": {
                            "left": {
                                "device": {
                                    "devices": [
                                        "6410c370-aae0-4b03-9cd0-4553260429b0"
                                    ],
                                    "component": "main",
                                    "capability": "thermostatMode",
                                    "attribute": "thermostatMode"
                                }
                            },
                            "right": {
                                "string": "cool"
                            }
                        }
                    },
                    {
                        "greaterThan": {
                            "left": {
                                "device": {
                                    "devices": [
                                        "3dd70f13-6e94-4c2c-9368-da186b2bd8cb"
                                    ],
                                    "component": "main",
                                    "capability": "relativeHumidityMeasurement",
                                    "attribute": "humidity",
                                    "trigger": "Always"
                                }
                            },
                            "right": {
                                "decimal": 49
                            }
                        }
                    }
                ],
                "then": [
                    {
                        "command": {
                            "devices": [
                                "d79709d8-5815-461f-afcd-8d8d295927f3"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "switch",
                                    "command": "on"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        {
            "if": {
                "lessThan": {
                    "left": {
                        "device": {
                            "devices": [
                                "3dd70f13-6e94-4c2c-9368-da186b2bd8cb"
                            ],
                            "component": "main",
                            "capability": "relativeHumidityMeasurement",
                            "attribute": "humidity",
                            "trigger": "Always"
                        }
                    },
                    "right": {
                        "decimal": 47
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "d79709d8-5815-461f-afcd-8d8d295927f3"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "switch",
                                    "command": "off"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        {
            "if": {
                "not": {
                    "between": {
                        "value": {
                            "time": {
                                "reference": "Now"
                            }
                        },
                        "start": {
                            "time": {
                                "reference": "noon",
                                "offset": {
                                    "value": {
                                        "integer": -270
                                    },
                                    "unit": "Minute"
                                }
                            }
                        },
                        "end": {
                            "time": {
                                "reference": "noon",
                                "offset": {
                                    "value": {
                                        "integer": 360
                                    },
                                    "unit": "Minute"
                                }
                            }
                        }
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "d79709d8-5815-461f-afcd-8d8d295927f3"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "switch",
                                    "command": "off"
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}
1 Like

Thanks for the reply @mrfitz98, I fixed the syntax and managed to post it as a rule. it is still looping though and sending the switches into a crazy frenzy of on/off states. if i split the code in the centre and make 2 rules it works . eg.

rule 1
if switch on, then lock locked
if switch off, then lock unlocked

rule 2
if door locked, then switch on
if door unlocked, then switch off

I tried the following
if switch on, then lock locked
if switch off, then lock unlocked
if door locked, then switch on

{
    "name": "If front door is switch changes, sync virtual door lock ",
    "actions": [
        {
            "if": {
                "equals": {
                    "right": {
                        "device": {
                            "devices": [
                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                            ],
                            "component": "main",
                            "capability": "switch",
                            "attribute": "switch"
                        }
                    },
                    "left": {
                        "string": "on"
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "lock",
                                    "command": "lock"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        {
            "if": {
                "equals": {
                    "right": {
                        "device": {
                            "devices": [
                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                            ],
                            "component": "main",
                            "capability": "switch",
                            "attribute": "switch"
                        }
                    },
                    "left": {
                        "string": "off"
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "lock",
                                    "command": "unlock"
                                }
                            ]
                        }
                    }
                ]
            }
        },
        {
            "if": {
                "equals": {
                    "right": {
                        "device": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "component": "main",
                            "capability": "lock",
                            "attribute": "lock"
                        }
                    },
                    "left": {
                        "string": "locked"
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "switch",
                                    "command": "on"
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}

after I added the 3rd if statement, the door switch started toggling. I switch the switch to off, the lock shows unlocked briefly and then the switch changes back to on and the lock changes back to locked.

should this code be written as else [if] or does it need an end if or stop after each statement to exit the code and stop it looping back on itself?

Whoooo, I got it working thanks to a piece of code that @nayelyz posted in a thread (believe it or not) named Mirror behavior in routines running local - SmartApps & Automations - SmartThings Community. I feel kinda stoopid because I did search and didn’t find anything before I posted my original question. anyway, the trick was to use the changes command. this code should definitely be in the JSON example library

here is what I did

{
    "name": "Front door lock to VS front door lock sync",
    "actions": [
        {
            "if": {
                "changes": {
                    "equals": {
                        "left": {
                            "device": {
                                "devices": [
                                    "892d6707-57db-42a1-afdc-67cd02dc39db"
                                ],
                                "component": "main",
                                "capability": "switch",
                                "attribute": "switch"
                            }
                        },
                        "right": {
                            "string": "on"
                        }
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "lock",
                                    "command": "lock"
                                }
                            ]
                        }
                    }
                ],
                "else": [
                    {
                        "if": {
                            "changes": {
                                "equals": {
                                    "left": {
                                        "device": {
                                            "devices": [
                                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                                            ],
                                            "component": "main",
                                            "capability": "switch",
                                            "attribute": "switch"
                                        }
                                    },
                                    "right": {
                                        "string": "off"
                                    }
                                }
                            },
                            "then": [
                                {
                                    "command": {
                                        "devices": [
                                            "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                                        ],
                                        "commands": [
                                            {
                                                "component": "main",
                                                "capability": "lock",
                                                "command": "unlock"
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        },
        {
            "if": {
                "changes": {
                    "equals": {
                        "left": {
                            "device": {
                                "devices": [
                                    "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                                ],
                                "component": "main",
                                "capability": "lock",
                                "attribute": "lock"
                            }
                        },
                        "right": {
                            "string": "locked"
                        }
                    }
                },
                "then": [
                    {
                        "command": {
                            "devices": [
                                "892d6707-57db-42a1-afdc-67cd02dc39db"
                            ],
                            "commands": [
                                {
                                    "component": "main",
                                    "capability": "switch",
                                    "command": "on"
                                }
                            ]
                        }
                    }
                ],
                "else": [
                    {
                        "if": {
                            "changes": {
                                "equals": {
                                    "left": {
                                        "device": {
                                            "devices": [
                                                "3c90112b-adf1-4fb7-91ab-cbffde4de8f7"
                                            ],
                                            "component": "main",
                                            "capability": "lock",
                                            "attribute": "lock"
                                        }
                                    },
                                    "right": {
                                        "string": "unlocked"
                                    }
                                }
                            },
                            "then": [
                                {
                                    "command": {
                                        "devices": [
                                            "892d6707-57db-42a1-afdc-67cd02dc39db"
                                        ],
                                        "commands": [
                                            {
                                                "component": "main",
                                                "capability": "switch",
                                                "command": "off"
                                            }
                                        ]
                                    }
                                }
                            ]
                        }
                    }
                ]
            }
        }
    ]
}

its all so simple…when you know how

With a Rule like that you have to bear in mind that the three ‘if’ blocks are not independent. If you turn the switch on, for example, it will execute all three blocks, not just the last one,

The other thing to watch out for is the behaviour of virtual devices. I always create virtual switches that only propagate changes, so the Rules only ever see ‘on / off /on / off / …’ etc. However the stock cloud and local virtual switches mimic the behaviour of the old DTH and so Rules can see ‘on / on / on / on / off / off / off / …’. So if you don’t consider that you can be creating infinite loops.

Update: I believe I was incorrect to include the cloud devices in with the local devices above.

You have discovered the changes condition, which can protect you from the variable device behaviour. You’ll also sometimes see that the conditions have their own properties tucked away. For example, greaterThan has a changesOnly boolean. Leave it on false and the condition behaves as you might expect, with the Rule being triggered every time it is evaluated. Set it to true and it only triggers when the condition result changes from false to true, like having its own built in changes condition.

3 Likes

hey Graham, thanks for the explanation. it’s hard trying to learn how to write code in JSON as well as in YAML for the google automation editor. For example, the same thing in the google editor is this which seems so much easier for a newbie like me.

metadata:
  name: ST Sync - Towel Rail
  description: When a switch is moved to the on position, turn on the towel rail.
automations:
  - starters:
      - type: device.state.OnOff
        device: Towel rail - Bathroom
        state: on
        is: true
    actions:
      - type: device.command.OnOff
        devices:
          - VS Towel rail - Switch room
        on: true

  - starters:
      - type: device.state.OnOff
        device: VS Towel rail - Switch room
        state: on
        is: true
    actions:
      - type: device.command.OnOff
        devices:
          - Towel rail - Bathroom
        on: true

  - starters:
      - type: device.state.OnOff
        device: Towel rail - Bathroom
        state: on
        is: false
    actions:
      - type: device.command.OnOff
        devices:
          - VS Towel rail - Switch room
        on: false

  - starters:
      - type: device.state.OnOff
        device: VS Towel rail - Switch room
        state: on
        is: false
    actions:
      - type: device.command.OnOff
        devices:
          - Towel rail - Bathroom
        on: false

anyway, thanks again guys for all the help

Have you seen any issues with your syncing?

I have tried similar syncing two devices, they were two light switches(house had an extra light switch for just an outlet in the living room and I decided it was better to sync it to use with the living room light and have the outlet powered all the time)

I did something similar

If Light A Changes to On, then If Light B Is Off, then turn Light B On
Else If Light B is On, then turn Light B Off

Then I made another separate rule, not in the same file with this

If Light B Changes to On, then If Light A Is Off, then turn Light A On
Else If Light A is On, then turn Light A Off

I’ve had a couple instances where it would cycle on/off real quick.

I read your Rules as:

IF
  Light A Changes to ON
THEN
  IF 
    Light B Is OFF
  THEN
    turn Light B ON
  ENDIF
ELSE
  IF
    Light B is ON
  THEN
    turn Light B OFF
  ENDIF
ENDIF

If that is the case then I might expect problems because the ELSE block for ‘IF Light A Changes to ON’ applies when absolutely any event triggers the piston other than Light A changing from Off to On. This would include:

  1. Light A going from ON to OFF.
  2. Light A going from ON to ON.
  3. Light A going from OFF to OFF.
  4. Any ON or OFF event from Light B.

Option 1 is what you want. 2 and 3 might not be possible for your device. 4 may happen unless you explicitly set the Light B device operands not to trigger the Rule (the default behaviour is not documented so it is better to assume the worst case).

A separate ‘IF Light A changes to OFF’ block could be used instead of the ELSE.

Hey, the code I posted seems to be working well. I was having issues but that was to do with the lock dropping offline in Google but as soon as its state updated in Google it synced with ST.

Could the problem you are having be because if light A changes to on and if light B is off, then turn light B on (rule 1) then this triggers rule 2 as light B changed to on so the second rule would turn light A off resulting in an on/off cycle