IFTTT api  [Solved]

Easy to use, 100% Lua-based event scripting framework.

Moderator: leecollings

Post Reply
pvklink
Posts: 822
Joined: Wednesday 12 November 2014 15:01
Target OS: Raspberry Pi / ODroid
Domoticz version: latest b
Contact:

IFTTT api

Post by pvklink »

Last thing on my domotica to do list is to update the IFTTT api script (see included).
My domoticz configuration works with google home and json calls and a script IFTTTVAR made by @waaren.
This works 100%.

Main functionality is to update a user variable and a IFTTT script reads the uservariable and executes switch or group actions based on the data in the uservar. The structure of the uservar is for example: denon_volume/Off/off/undefined

I like to use
- a formal json format for this
- possibility (see above) to do more device or group actions based on one json message.

Some possible datastructures (please adjust these to confirm to json standards)
Functionality: group on/off, on/off based on ids and names, delays, repeats after etc. and more actions in one body

{
{"object":"Schakelgroepen","onoff":"On","item_name":"Sfeerverlichting binnen"},
{"object":"lantaarn","onoff":"On",afterMin:"1",forMin:"10"},
{"object":"usb lamp","onoff":"On","item_name":"Flame"},
{ "object":"denon_volume","onoff":"On","item_name":"40","stype":"2"}
}

Problem is that these wishes are to complex to program (for me).
My skills dont reach further then making easy scripts and adjust complex scripts made by pro's like you and using them more then 100% with more then 50 objects...

simular scripts
https://www.domoticz.com/forum/viewtopi ... 59&t=28655

Current working code

Code: Select all

local iftttvar = "iftttvar"  

return 
{
    on =    
    { variables = { iftttvar },   
    },
                    
    logging =
    {   
        level = domoticz.LOG_ERROR,
        marker = 'ifttt',
   },

    execute = function(dz, item, info)
        local validBaseTypes = { device=true, group=true, scene=true, uservariable=false, camera=false }
        local validActions = { ON=true, OFF=true }
        local result = dz.utils.stringSplit(item.value,'/')                                                       -- aangepast was geen local
        local name = result[1]
        local action = string.upper(result[2])
        local parm1 = result[3]
        local stype = result[4]

        local logcode = 3
        local messageTable = {}
        local add = 'add'
        local del = 'del'
        local chg = 'chg'
        
        local function globalMessage(action, message, logcode)
 
            if logcode == nil then logcode = 3 end
 
            if action == add and logcode > 0 then
                messageTable = dz.helpers.addMessage(dz, item, info, message, messageTable, logcode)
            elseif action == del then
                dz.globalData.mylogging = message
                dz.devices('timerlog').updateText(message)
            elseif action == chg then 
                dz.helpers.dumpMessages(dz, messageTable)
            end
 
        end

        if parm1 == nil then parm1 = 'undefined' end -- par1 is de selectornaam of selectorlevel
        if stype == nil then stype = 'undefined' end -- stype is of par naam 1 of level is 2

        local function getBaseType(name)
            for i, tuple in ipairs(_G.domoticzData) do 
                if tuple.name == name then 
                    return tuple.baseType, tuple.id 
                end
            end
        end
        
        local baseType, idx = getBaseType(name)
        if baseType and validBaseTypes[baseType] then
            local target 
            if baseType == 'device' then 
                target = dz.devices(name)
            elseif baseType == 'group' then 
                target = dz.groups(name)
            elseif baseType == 'scene' then 
                target = dz.scenes(name) 
                validActions.OFF = nil -- No Off action for scenes
            end
            if validActions[action] then
                if action == 'ON' then 
                    if parm1 ~= 'undefined' then
                        if stype == '1' or stype == 'undefined' then
                            globalMessage(add, ' IFTTT AAN: ,settings: ' .. tostring(item.value), logcode)
                            target.switchSelector(parm1) --parm selector_name 
                        elseif stype == '2' then
                            globalMessage(add, ' IFTTT AAN: settings: ' .. tostring(item.value), logcode)
                            target.switchSelector(tonumber(parm1)) --parm selector_id
                        else -- foute waarde
                            globalMessage(add, ' IFTTT AAN: foute waarde, settings: ' .. tostring(item.value), logcode)
                        end
                    else
                        globalMessage(add, ' IFTTT AAN: settings: ' .. tostring(item.value), logcode) 
                        target.switchOn() 
                    end
                elseif action == 'OFF' then 
                    globalMessage(add, ' IFTTT AAN: settings: ' .. tostring(item.value), logcode)
                    target.switchOff() 
                else -- no other actions implemented yet
                    globalMessage(add, 'IFTTT AAN: foute action waarde, settings: ' .. action, logcode) 
                end
            else 
                globalMessage(add, 'IFTTT AAN: action: '  .. action .. ' is not implemented for '  .. baseType .. ' Name ' .. name .. '!', logcode)
 
            end
        elseif baseType then
            globalMessage(add, ' IFTTT AAN: onbekende basetype: ' .. baseType, logcode)    --  dz.LOG_ERROR evt ook doorgeven 
 
        else
            globalMessage(add, ' IFTTT AAN: name not found!: ' .. name, logcode)
 
        end
        globalMessage(chg) -- dump

    end
}
Raspberry (raspbian on rpi 3) , Domoticz Beta, dzVents , RFXtrx433e, P1, Hue, Yeelight, Zwave+, X10, ESP(easy), MQTT,Weather Underground, System Alive Checker, Domoticz Remote Server to RPI with Google Assistant,
Jablotron connection, Ikea
User avatar
waaren
Posts: 6028
Joined: Tuesday 03 January 2017 14:18
Target OS: Linux
Domoticz version: Beta
Location: Netherlands
Contact:

Re: IFTTT api

Post by waaren »

pvklink wrote: Saturday 17 August 2019 11:51 My domoticz configuration works with google home and json calls and a script IFTTTVAR
I like to use
- a formal json format for this
- possibility (see above) to do more device or group actions based on one json message.
To convert the script to use JSON is not complex but the number of chars in a domoticz uservariable is limited so don't expect to be able to send many commands in one pass. 3-4 will probably be max.
Some possible datastructures (please adjust these to confirm to json standards)
Functionality: group on/off, on/off based on ids and names, delays, repeats after etc. and more actions in one body

Code: Select all

{
{"object":"Schakelgroepen","onoff":"On","item_name":"Sfeerverlichting binnen"},
{"object":"lantaarn","onoff":"On",afterMin:"1",forMin:"10"},
{"object":"usb lamp","onoff":"On","item_name":"Flame"},
{ "object":"denon_volume","onoff":"On","item_name":"40","stype":"2"}
}

Because of the size limit (200 chars) you will probably want to use something like

Code: Select all

[{"obj":"Schakelgroepen","act":"On","name":"Sfeer verl"},
{"obj":"lantaarn","act":"On","fM":1,"aM":10},
{"obj":"usblamp","act":"On","name":"Flame"},
{"obj":"denon_volume","act":"On","name":40,"stype":2}]
 
How should I interpret object / name / stype ?
Probably best to take this to PM until we have a working solution and then post it here.

[/quote]
Debian buster, bullseye on RPI-4, Intel NUC.
dz Beta, Z-Wave, RFLink, RFXtrx433e, P1, Youless, Hue, Yeelight, Xiaomi, MQTT
==>> dzVents wiki
pvklink
Posts: 822
Joined: Wednesday 12 November 2014 15:01
Target OS: Raspberry Pi / ODroid
Domoticz version: latest b
Contact:

Re: IFTTT api

Post by pvklink »

name is levelname, item is a better description...
i use stype for the case that a devicelevel(item) isnt a name but an number, see script
i know, this isnt a nice solotion, the script should figure this out....
so better is when an item can be option "tekst" or "1" and delete stype

[{"obj":"Schakelgroepen","act":"On","item":"Sfeer verl"},
{"obj":"lantaarn","act":"On","fM":1,"aM":10},
{"obj":"usblamp","act":"On","item":"Flame"},
{"obj":"denon_volume","act":"On","item":40}]

Code: Select all

local iftttvar = "iftttvar"  

return 
{
    on =    
    { variables = { iftttvar },   
    },
                    
    logging =
    {   
        level = domoticz.LOG_ERROR,
        marker = 'ifttt',
   },

    execute = function(dz, item, info)
        local validBaseTypes = { device=true, group=true, scene=true, uservariable=false, camera=false }
        local validActions = { ON=true, OFF=true }
        local result = dz.utils.stringSplit(item.value,'/')                                                       -- aangepast was geen local
        local name = result[1]
        local action = string.upper(result[2])
        local parm1 = result[3]
        local stype = result[4]

        local logcode = 3
        local messageTable = {}
        local add = 'add'
        local del = 'del'
        local chg = 'chg'
        
        local function globalMessage(action, message, logcode)
 
            if logcode == nil then logcode = 3 end
 
            if action == add and logcode > 0 then
                messageTable = dz.helpers.addMessage(dz, item, info, message, messageTable, logcode)
            elseif action == del then
                dz.globalData.mylogging = message
                dz.devices('timerlog').updateText(message)
            elseif action == chg then 
                dz.helpers.dumpMessages(dz, messageTable)
            end
 
        end

        if parm1 == nil then parm1 = 'undefined' end -- par1 is de selectornaam of selectorlevel
        if stype == nil then stype = 'undefined' end -- stype is of par naam 1 of level is 2

        local function getBaseType(name)
            for i, tuple in ipairs(_G.domoticzData) do 
                if tuple.name == name then 
                    return tuple.baseType, tuple.id 
                end
            end
        end
        
        local baseType, idx = getBaseType(name)
        if baseType and validBaseTypes[baseType] then
            local target 
            if baseType == 'device' then 
                target = dz.devices(name)
            elseif baseType == 'group' then 
                target = dz.groups(name)
            elseif baseType == 'scene' then 
                target = dz.scenes(name) 
                validActions.OFF = nil -- No Off action for scenes
            end
            if validActions[action] then
                if action == 'ON' then 
                    if parm1 ~= 'undefined' then
                        if stype == '1' or stype == 'undefined' then
                            globalMessage(add, ' IFTTT AAN: ,settings: ' .. tostring(item.value), logcode)
                            target.switchSelector(parm1) --parm selector_name 
                        elseif stype == '2' then
                            globalMessage(add, ' IFTTT AAN: settings: ' .. tostring(item.value), logcode)
                            target.switchSelector(tonumber(parm1)) --parm selector_id
                        else -- foute waarde
                            globalMessage(add, ' IFTTT AAN: foute waarde, settings: ' .. tostring(item.value), logcode)
                        end
                    else
                        globalMessage(add, ' IFTTT AAN: settings: ' .. tostring(item.value), logcode) 
                        target.switchOn() 
                    end
                elseif action == 'OFF' then 
                    globalMessage(add, ' IFTTT AAN: settings: ' .. tostring(item.value), logcode)
                    target.switchOff() 
                else -- no other actions implemented yet
                    globalMessage(add, 'IFTTT AAN: foute action waarde, settings: ' .. action, logcode) 
                end
            else 
                globalMessage(add, 'IFTTT AAN: action: '  .. action .. ' is not implemented for '  .. baseType .. ' Name ' .. name .. '!', logcode)
 
            end
        elseif baseType then
            globalMessage(add, ' IFTTT AAN: onbekende basetype: ' .. baseType, logcode)    --  dz.LOG_ERROR evt ook doorgeven 
 
        else
            globalMessage(add, ' IFTTT AAN: name not found!: ' .. name, logcode)
 
        end
        globalMessage(chg) -- dump

    end
}
Raspberry (raspbian on rpi 3) , Domoticz Beta, dzVents , RFXtrx433e, P1, Hue, Yeelight, Zwave+, X10, ESP(easy), MQTT,Weather Underground, System Alive Checker, Domoticz Remote Server to RPI with Google Assistant,
Jablotron connection, Ikea
User avatar
waaren
Posts: 6028
Joined: Tuesday 03 January 2017 14:18
Target OS: Linux
Domoticz version: Beta
Location: Netherlands
Contact:

Re: IFTTT api  [Solved]

Post by waaren »

pvklink wrote: Saturday 17 August 2019 11:51 Last thing on my domotica to do list is to update the IFTTT api script (see included).
- a formal json format for this
- possibility (see above) to do more device or group actions based on one json message.
Please find a working version below. Thx for constructive feedback and extensive testing by @pvklink.

Code: Select all

--[[
syntax of JSON in uservar: [{"obj":"IFvar","set":"TestString"},{"obj":"IFswitch","act":"On","fS":10,"aS":10,"si":true},{"obj":"IFSelector","lbl":"Level1"},{"obj":"IFScene","act":"On"}]
or 
[{"obj":"IFvar","set":"TestString"},{"obj":"message from ifttt","msg":["message to all"]},{"obj":"IFSelector","lbl":"Level1"},{"obj":"IFScene","act":"On"}]

max. number of chars = 200
  
every action request in the json should have obj/object and max. one of (act/action, lbl/label, msg/message, set or upd/update, ). 
Other controls (aS/afterSec, fS/ forSec, rAS/repeatAfterSec, si/silent are optional.


if message/msg is the requested action the object field is used as subject and subtable [{}]

            mandatory       line.object    message subject
            mandatory       line.message[1] message body
            optional or nil line.message[2] message priority  (-2=LOW, -1=MODERATE, 0=NORMAL, 1=HIGH, 2=EMERGENCY)
            optional or nil line.message[3] message sound (string like 'pushover','bike','none')
            optional or nil line.message[4] extra 
            optional or nil line.message[5] subsystem or table with subsystems
            
            examples 
            [{"obj":"message from ifttt","msg":["message to all notification systems with default priority"]}]
            [{"obj":"message from ifttt","msg":["message to pushover with high priority",1,null,null,"pushover"]}]
            [{"obj":"message from ifttt","msg":["message to pushover and gcm with low priority",-2,null,null,["pushover","gcm"] ]}]

if OSCommand/os is the requested action, the object field is used program / scriptname. name must be full qualified e.g. "/home/pi/scripts/myScript.sh"

]]--
local iftttvar = "IFTTTvar"  

return 
{
    on =    
    { 
        variables = { iftttvar },   
    },
                    
    logging =
    {   
        level = domoticz.LOG_DEBUG,
        marker = 'ifttt',
   }, 

    execute = function(dz, item)
        
        local function makeLongKeys(str)
            local shortNames = {    
                                    act="action",  -- optional: Can be On, Off or OS/OScommand: 
                                                   -- On; valid for switches and groups and scenes and scenes; Off; valid for switches and groups
                                    aS="afterSec", -- optional: 
                                    fS="forSec",   -- optional: Only valid for standard (On/Off) switches 
                                    lbl="label",   -- optional: levelName or number for selector type switches
                                    msg="message", -- Optional: obj must be set to subject 
                                    obj="object",  -- Mandatory and must be unique in domoticz: name for device, scene, group or uservariable.  
                                    os="OSCommand", -- if os / OSCommand is the requested action, obj must be set to full qualified script /program. 
                                    rAS="repeatAfterSec", -- optional: Only valid for standard (On/Off) switches 
                                    set="set",  -- Optional: update uservariable 
                                    si="silent",  -- Optional: use "si":true in JSON
                                    upd="update", -- Optional: Only valid for devices (set nValue, sValue)
                                    
                               }
 
            for short, long in pairs(shortNames) do
                str = str:gsub('%f[%a]' .. short .. '%f[%A]' ,long) -- this will only replace short with long if short is a whole word
            end
            return str  
        end
        
        local function getBaseType(name)
            for i, tuple in ipairs(_G.domoticzData) do 
                if tuple.name == name then 
                    return tuple.baseType, tuple.id 
                end
            end
        end
        
        local function makeTarget(baseType, name)
            if baseType == 'device' then 
                return dz.devices(name)
            elseif baseType == 'group' then 
                return dz.groups(name)
            elseif baseType == 'scene' then 
                return dz.scenes(name) 
            elseif baseType == 'uservariable' then 
                return dz.variables(name)
            end
        end

        local function doLabelVariant(line, target)
            if line.afterSec and line.silent then 
                target.switchSelector(line.label).silent().afterSec(line.afterSec)
            elseif line.afterSec then
                target.switchSelector(line.label).afterSec(line.afterSec)
           elseif line.silent then
                target.switchSelector(line.label).silent()
           else
                target.switchSelector(line.label)
           end
        end
        
        local function doUpdateVariant(line, target)
            if line.afterSec and line.silent then 
                target.update(line.update).silent().afterSec(line.afterSec)
            elseif line.silent then
                target.update(line.update).silent()
            elseif line.afterSec then
                target.update(line.update).afterSec(line.afterSec)
            else
                target.update(line.update)
            end
        end
        
        local function doSetVariant(line, target)
            if line.afterSec and line.silent then 
                target.set(line.set).silent().afterSec(line.afterSec)
            elseif line.silent then
                target.set(line.set).silent()
            elseif line.afterSec then
                target.set(line.set).afterSec(line.afterSec)
            else
                target.set(line.set)
            end
        end
        
        local function doOnVariant(line, target)
            if line.afterSec and line.forSec and line.silent then 
                target.switchOn().silent().afterSec(line.afterSec).forSec(line.forSec)
            elseif line.afterSec and line.repeatAfterSec and line.silent then 
                target.switchOn().silent().afterSec(line.afterSec).repeatAfterSec(line.repeatAfterSec)
            elseif line.afterSec and line.silent then
                target.switchOn().silent().afterSec(line.afterSec)
           elseif line.afterSec then
                target.switchOn().afterSec(line.afterSec)
           elseif line.silent then
                target.switchOn().silent()
           else
                target.switchOn()
           end
        end
        
        local function doOffVariant(line, target)
            if line.afterSec and line.forSec and line.silent then 
                target.switchOff().silent().afterSec(line.afterSec).forSec(line.forSec)
            elseif line.afterSec and line.repeatAfterSec and line.silent then 
                target.switchOff().silent().afterSec(line.afterSec).repeatAfterSec(line.repeatAfterSec)
            elseif line.afterSec and line.silent then
                target.switchOff().silent().afterSec(line.afterSec)
            elseif line.afterSec then
                target.switchOff().afterSec(line.afterSec)
            elseif line.silent then
                target.switchOff().silent()
            else
                target.switchOff()
            end
        end
        
        local function doStopVariant(line, target)
            if line.afterSec and line.silent then 
                target.stop().silent().afterSec(line.afterSec)
            elseif line.afterSec then
                target.stop().afterSec(line.afterSec)
            elseif line.silent then
                target.stop().silent()
            else
                target.stop()
            end
        end
        
        local function doNotificationVariant(line)
            dz.notify(line.object,line.message[1],line.message[2] or nil,line.message[3] or nil,line.message[4] or nil,line.message[5] or nil)
        end

        local function doOSVariant(osCommand)
            os.execute('sudo ' .. osCommand .. ' &')
            dz.utils.dumpTable(result)
        end

        local function doTasks(result)
            for _, line in ipairs(result) do
                if line.message then
                    doNotificationVariant(line)
                elseif line.action == 'OSCommand' then
                    doOSVariant(line.object)
                else
                    local baseType, idx = getBaseType(line.object)
                    target = makeTarget(baseType, line.object)
                    if not(target) then 
                       dz.log('no device, scene, group or var found',dz.LOG_ERROR)
                       return 
                    end 
                    if line.action and line.action == 'Off' then -- for switch type devices and groups
                        doOffVariant(line, target)
                    elseif line.action and line.action == 'On' then -- for switch type devices, scenes and groups
                        doOnVariant(line, target)
                    elseif line.action and line.action == 'OSCommand' then 
                        doOSVariant(line, target)
                    elseif line.action and line.action == 'Stop' then -- for type blinds 
                        doStopVariant(line, target)
                    elseif line.label then -- for selector type devices
                        doLabelVariant(line, target) 
                    elseif line.set then -- for uservariables
                        doSetVariant(line, target)
                    elseif line.update then -- for devices to set nValu, sValue
                        doUpdateVariant(line, target)
                    end
                end
            end
        end

        -- main 
        local result = dz.utils.fromJSON(makeLongKeys(item.value))
        doTasks(result)
        
    end 
}
Debian buster, bullseye on RPI-4, Intel NUC.
dz Beta, Z-Wave, RFLink, RFXtrx433e, P1, Youless, Hue, Yeelight, Xiaomi, MQTT
==>> dzVents wiki
pvklink
Posts: 822
Joined: Wednesday 12 November 2014 15:01
Target OS: Raspberry Pi / ODroid
Domoticz version: latest b
Contact:

Re: IFTTT api

Post by pvklink »

@waaren, thanks for the wunderful script you made for me!

I use this script myself as a sort off "API" between domoticz and MQTTT.

My usage/Procedure:
1. i give a Google Assistant command
2. IFTTT/webhooks pics it up and send a json message via IFTTT to my NODE-RED broker
3. NODE-RED makes this message suitable for MQTTT
4. MQTTT send this message to domoticz via the MQTTT-domoticz hardware plugin.
Result is that a uservar(IFTTTvar) in domoticz is filled with a json message (the content off the IFTTT webhook)
5. when the uservar is changed, the script automatically fires and executes functions like:
an OS command
a switch command
a switch selector command
fills a uservar
sends a message
executes a group or scene

AND yes! This can be executed much easier by:
1. call a domoticz API/JSON URL's for a spectific device and dont use this script at all!
2. call a domoticz API/JSON URL's for an update of the mentioned uservar(IFTTTvar) so that the script is executed directly. You dont need NODERED en MQTTT for that.

Why creating an extra script?
- Advantage of this way of executing is that you can add extra options yourself (or in my way @waaren did that for me) that default domoticz/json dont offer....

Why using IFTTT and webhooks?,
- because Google needs help with sending json url's and send it to domoticz.

Why using NODEred and MQTTT.
- You dont need this, i like doing it the hard way and mess with new technology...

Finnaly
Fo those who also wants to play with nodered and mqttt. I used the default install doc's from the internet and use them on the same device as domoticz, a rpi 3b.

Below you can find my NODE-RED flow configuration. In this configuration i also described how to config your IFTTT/webhook applet/service and i even have some examples that you have to adjust and after that you can test nodered/mqttt/domoticz-script manually without Google assistant/ifttt.
Naamloos.png
Naamloos.png (22.81 KiB) Viewed 2551 times
NODERED / IFTTT

Code: Select all

[{"id":"4b581568.d34f6c","type":"comment","z":"612f3ecd.84333","name":"description how to setup ifttt webhook","info":"in ifttt webhook use:\nurl = https://<user>:<pwd>@<webadres>:1880/pvkvar2\nmethod = post\ncontenttype = application/json\nbody= [{\"obj\":\"lantaarn\",\"act\":\"On\"},{\"obj\":\"Lamp tv links\",\"act\":\"On\"}]","x":230,"y":80,"wires":[]}]
Raspberry (raspbian on rpi 3) , Domoticz Beta, dzVents , RFXtrx433e, P1, Hue, Yeelight, Zwave+, X10, ESP(easy), MQTT,Weather Underground, System Alive Checker, Domoticz Remote Server to RPI with Google Assistant,
Jablotron connection, Ikea
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest