Jump to content

HC3 QuickApps coding - tips and tricks


Recommended Posts

W dniu 12.09.2020 o 12:49, jgab napisał:

Here is a version of HC2Proxy that supports rollerShutter better - the buttons on the childDevice sends the right commands back to the HC2 device.

v.1.10

HC2Proxy.fqa 53 kB · 5 pobrań

 

I have the same problem again, it looks like a problem with the authorization of HCL to HC3, even though I did not change anything, HCL stopped sending triggers to HC3 ...
from the hc3 level I can switch the device status in HCL ... but HCL does not send messages to HC3 ...
After restarting the QA, the HC3 obviously reads the states of the devices in the HCL
Any ideas to solve the problem?

Link to post
Share on other sites
  • Replies 876
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Popular Posts

This thread is most about QuickApp tricks and usually requires some deeper understanding of Lua. I'm going to make an attempt to do something more tutorial wise that could work for newcomers to Lua. I

A thread to share some coding techniques for QuickApps?  Because QAs are "long running scenes" (they don't have to be loaded and restarted for every event) - it is actually worthwhile to build up

The anatomy of QuickApps – Part 2 (Part 1)   Disclaimer1: We are now venturing into undocumented land. That means that Fibaro is free to change how things work at any time. Well, Fibaro

Posted Images

6 hours ago, Rover said:

You are right (as always):

 
   
id 88
name "OverloopSwitch (HC2)"
roomID 219
view []
type "com.fibaro.remoteSceneController"
baseType "com.fibaro.remoteController"
enabled true
visible true
isPlugin true
parentId 73
viewXml false
configXml false
interfaces  
0 "quickAppChild"
1 "zwaveSceneActivationEvent"
properties  
availableScenes []
categories  
0 "remotes"
dead false
deadReason ""
deviceControlType 0
deviceIcon 103
emailNotificationID 0
emailNotificationType 0
log ""
logTemp ""
manufacturer ""
model ""
pushNotificationID 0
pushNotificationType 0
quickAppVariables  
0  
name "className"
value "sceneActivationDevice"
1  
name "id"
value 306
2  
name "HC2PROXY"
value "306:com.fibaro.remoteSceneController"
saveLogs true
smsNotificationID 0
smsNotificationType 0
useEmbeddedView true
userDescription ""
actions {}
created 1600082539
modified 1600082539
sortOrder 55

But this device does not react in a HC3 test scene, neither do I see a trigger in HC2 when pushing button 1 of the remote controller1x or 2x.

Spoiler

screenshot(13).png.b60ba484b94750703ad078bc4850e0bd.png

 

Link to post
Share on other sites
On 9/13/2020 at 5:36 PM, Rover said:
On 9/13/2020 at 4:43 PM, jgab said:

I meant in the header of the HC2  scene


--[[
%% events 
263 CentralSceneEvent
--]]

 

Hoeray! That works!

I had used 263 value.

TNX jgab 👏

It does not work anymore...

Spoiler

screenshot(14).thumb.png.b0cb1bd45c02cfc22274f5069982e91f.png

 

Link to post
Share on other sites
12 hours ago, michal85pl said:

I have the same problem again, it looks like a problem with the authorization of HCL to HC3, even though I did not change anything, HCL stopped sending triggers to HC3 ...
from the hc3 level I can switch the device status in HCL ... but HCL does not send messages to HC3 ...
After restarting the QA, the HC3 obviously reads the states of the devices in the HCL
Any ideas to solve the problem?

Can you see the console/log on the HCL? 

It logs "Updating ..." when it tries to send a message to the HC3 and it logs the error if the http request failed.

12 hours ago, Rover said:

But this device does not react in a HC3 test scene, neither do I see a trigger in HC2 when pushing button 1 of the remote controller1x or 2x.

  Hide contents

screenshot(13).png.b60ba484b94750703ad078bc4850e0bd.png

 

 

How does your scene header look like on the HC2? That you don't get a trigger indicates that there is something wrong with that, or?

Link to post
Share on other sites
12 minutes ago, jgab said:

How does your scene header look like on the HC2? That you don't get a trigger indicates that there is something wrong with that, or?

Spoiler

306 CentralSceneEvent

Obviously is CentralSceneEvent not correct, but what should it be for a ZWAVE.ME WALLC-S ?

Link to post
Share on other sites
40 minutes ago, Rover said:
  Hide contents

306 CentralSceneEvent

Obviously is CentralSceneEvent not correct, but what should it be for a ZWAVE.ME WALLC-S ?

--[[

%% properties

306 sceneActivation

--]]

Link to post
Share on other sites
14 minutes ago, jgab said:

--[[

%% properties

306 sceneActivation

--]]

Test scene works now, TNX!

 

Now the issue that 263 CentralSceneEvent device did work, but not anymore in this version 1.10. No trigger in HC2 either.

Link to post
Share on other sites
12 godzin temu, jgab napisał:

Can you see the console/log on the HCL? 

It logs "Updating ..." when it tries to send a message to the HC3 and it logs the error if the http request failed.

 

How does your scene header look like on the HC2? That you don't get a trigger indicates that there is something wrong with that, or?

I think I found the cause of the problem. Most likely, HCL cannot get to HC3 if https access is set in HC3, it only works for http. Can You verify this in Yours environment?

Link to post
Share on other sites
On 9/15/2020 at 9:41 PM, michal85pl said:

I think I found the cause of the problem. Most likely, HCL cannot get to HC3 if https access is set in HC3, it only works for http. Can You verify this in Yours environment?

For secure access to the HC3 do not use its IP but use the gateway’s serial number. like. https://hc3-00000001 

 

For example: 

--for https Use
local address = 'https://hc3-00000001/api/devices'
--for http Use
local address = 'http://'..IP..'/api/devices'

 

Link to post
Share on other sites
On 9/15/2020 at 9:41 PM, michal85pl said:

I think I found the cause of the problem. Most likely, HCL cannot get to HC3 if https access is set in HC3, it only works for http. Can You verify this in Yours environment?

 

You could try to change the http.request call in the HC2 scene script to use https and see if it works.

local baseURI = format("https://%s",HC3_IP)                --<-- change to https
local creds = 'Basic '..base64(HC3_USER..":"..HC3_PWD)
local function callHC3(uri,method,args,cont)
  local url = baseURI..uri
  net.HTTPClient():request(url,{
      options = {
        method = method or "GET",  
        checkCertificate = false,                          --<-- add option
        headers={
          ['Authorization'] = creds,
          ["Accept"] = 'application/json',
          ["X-Fibaro-Version"] = "2"
        },
        data = args and json.encode(args)
      },
      success = function(status) if cont then cont(status) end end,
      error = function(status) fibaro:debug(status) end
    }
  )
end

 

Edited by jgab
  • Thanks 1
Link to post
Share on other sites
On 9/15/2020 at 9:57 AM, Rover said:

Now the issue that 263 CentralSceneEvent device did work, but not anymore in this version 1.10. No trigger in HC2 either.

Spoiler
    [210] = {name='VoordeurSwitch', class="sceneActivationDevice", type="com.fibaro.remoteSceneController"},
    [306] = {name='OverloopSwitch', class="sceneActivationDevice", type="com.fibaro.remoteSceneController"},
    [263] = {name='PortSwitch', class="centralSceneDevice", type="com.fibaro.remoteSceneController"},

I see that I made a mistake with devices 210 and 306: they should be of type com.fibaro.remoteController, but they are functioning well!

Device 263 had been functioning well (before changes), but not at the moment: no response with pushing button 1 HeldDown of device 263!

Changes I have made in HC2Proxy 1.10:

Spoiler
EXTRAS['centralSceneDevice'] = {
  properties= {centralSceneSupport = {   
      { keyAttributes = {"Pressed","Released","HeldDown","Pressed2","Pressed3"},keyId = 1 },
      { keyAttributes = {"Pressed","Released","HeldDown","Pressed2","Pressed3"},keyId = 2 },
      { keyAttributes = {"Pressed","Released","HeldDown","Pressed2","Pressed3"},keyId = 3 },
      { keyAttributes = {"Pressed","Released","HeldDown","Pressed2","Pressed3"},keyId = 4 },
      { keyAttributes = {"Pressed","Released","HeldDown","Pressed2","Pressed3"},keyId = 5 },
      { keyAttributes = {"Pressed","Released","HeldDown","Pressed2","Pressed3"},keyId = 6 },
    }},
  interfaces = {"zwaveCentralScene"},
}

changed to:

Spoiler
EXTRAS['centralSceneDevice'] = {
  properties= {centralSceneSupport = {   
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 1 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 2 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 3 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 4 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 5 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 6 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 7 },
      { keyAttributes = {"Pressed","Pressed2","HeldDown","Released","Pressed3"},keyId = 8 },
    }},
  interfaces = {"zwaveCentralScene"},
}

So i exchanged also Released with Pressed2.

What could be the problem?

 

Link to post
Share on other sites
15 hours ago, jgab said:

 

You could try to change the http.request call in the HC2 scene script to use https and see if it works.

 

local baseURI = format("https://%s",HC3_IP)                --<-- change to https and IP to name like: hc3-00001234
local creds = 'Basic '..base64(HC3_USER..":"..HC3_PWD)
local function callHC3(uri,method,args,cont)
  local url = baseURI..uri
  net.HTTPClient():request(url,{
      options = {
        method = method or "GET",  
        checkCertificate = false,
        data = json.encode(args),     --<-- by a put, data outside the headers?
        headers={
          ['Authorization'] = creds,
          ["Accept"] = 'application/json',
          ["X-Fibaro-Version"] = "2"
        }
      },
      success = function(status) if cont then cont(status) end end,
      error = function(status) fibaro:debug(status) end
    }
  )
end

Question, shouldn't the data be outside the header for a PUT command?

 

 

Edited by NLWaard
Link to post
Share on other sites
6 hours ago, NLWaard said:
local baseURI = format("https://%s",HC3_IP)                --<-- change to https and IP to name like: hc3-00001234
local creds = 'Basic '..base64(HC3_USER..":"..HC3_PWD)
local function callHC3(uri,method,args,cont)
  local url = baseURI..uri
  net.HTTPClient():request(url,{
      options = {
        method = method or "GET",  
        checkCertificate = false,
        data = json.encode(args),     --<-- by a put, data outside the headers?
        headers={
          ['Authorization'] = creds,
          ["Accept"] = 'application/json',
          ["X-Fibaro-Version"] = "2"
        }
      },
      success = function(status) if cont then cont(status) end end,
      error = function(status) fibaro:debug(status) end
    }
  )
end

Question, shouldn't the data be outside the header for a PUT command?

 

 

Yes, really strange. It is in my code but what I pasted here is wrong.

Link to post
Share on other sites
On 2/24/2020 at 11:13 PM, jgab said:

Here is a simple webhook QD.

The idea is that instead of having external apps continuously polling the HC3 for events, the app makes a call to the HC3 and register it's url for callbacks.

The HC3 will POST all events to the apps that have registered with it.

If an app doesn't answer to the POST it's just unregistered. 

If an app register twice it's detected and won't cause a duplicate post. Thus, a strategy for apps could be to re-register at intervals in case the HC3 QuickApp have been restarted.

To register, the external app does a POST to http://<HC3IP>/api/devices/<QD ID>/action/registerForEvents with the payload = {args = {"<callback url>"}}

 

Here is a simple NodeRed flow that register for call-backs and the receives events (just logs them)

  Reveal hidden contents

[{"id":"9c6b477.b091238","type":"tab","label":"Flow 2","disabled":false,"info":""},{"id":"106671b6.dc1e2e","type":"inject","z":"9c6b477.b091238","name":"Register for callback","topic":"","payload":"{\"args\":[\"http://192.168.1.50:1880/webhook\"]}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":110,"y":160,"wires":[["4928d15b.a0ac18"]]},{"id":"42b7ae29.b744a8","type":"http in","z":"9c6b477.b091238","name":"Incoming events","url":"/webhook","method":"post","upload":false,"swaggerDoc":"","x":110,"y":260,"wires":[["8f3e2131.1227e","803ddcc7.1dba58"]]},{"id":"8f3e2131.1227e","type":"debug","z":"9c6b477.b091238","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":510,"y":260,"wires":[]},{"id":"4928d15b.a0ac18","type":"http request","z":"9c6b477.b091238","name":"","method":"POST","ret":"txt","paytoqs":false,"url":"http://192.168.1.57/api/devices/39/action/registerForEvents","tls":"","proxy":"","authType":"basic","x":310,"y":160,"wires":[["ddc0b444.52d38"]]},{"id":"ddc0b444.52d38","type":"debug","z":"9c6b477.b091238","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":490,"y":160,"wires":[]},{"id":"803ddcc7.1dba58","type":"http response","z":"9c6b477.b091238","name":"","statusCode":"","headers":{},"x":270,"y":300,"wires":[]}]

 

Here is the code for the QD - by turning on/off the "Binary button" the loop is started/stopped

if dofile then
  dofile("fibaroapiHC3.lua")
  local cr = loadfile("credentials.lua"); if cr then cr() end
end

pollRef = nil
interval = 1000
subscribers = {}

function QuickApp:turnOff() 
  self:updateProperty("value",false); 
  if pollRef then clearInterval(pollRef); pollRef=nil end 
end
function QuickApp:turnOn() 
  self:updateProperty("value",true); 
  if not pollRef then pollRef=setInterval(pollEvents,interval) end 
end

function QuickApp:registerForEvents(url) -- can be called  post /devices/{deviceID}/action/registerForEvents {args:["<url>]}
  self:debug("Subsriber: ",url)
  subscribers[url]=true
end

function pushEvents(events)
  local payload = json.encode(events)
  local stat,res = pcall(function()
  local subs = {}
  for k,v in pairs(subscribers) do subs[k]=v end -- copy table as we may change it in the loop
  for url,_ in pairs(subs) do
    net.HTTPClient():request(url,
      {options = {
          headers = {['Accept']='application/json',['Content-Type']='application/json',['Connection']='keep-alive'},
          timeout=2000, data=payload, checkCertificate = false, method = 'POST'},
        success = function() print("=>"..url) end,
        error = function() print("X "..url); subscribers[url]=nil end -- forget subscribers that don't answer
      })
  end
  end) 
  if not stat then print(res) end
end

function pollEvents()
  lastRefresh = lastRefresh or 0
  local states = api.get("/refreshStates?last=" .. lastRefresh)
  if states then
    lastRefresh=states.last
    if states.events and #states.events>0 then pushEvents(states.events) end
  end
  --fibaro.emitCustomEvent('tickEvent')  -- hack because refreshState hang if no events...
end

function QuickApp:onInit()
  self:debug(string.format("Webhook (deviceId:%s)",plugin.mainDeviceId))
  self:turnOn()
end

 

 

but if there's an error in pollEvents the whole systems stops polling. How to prevent from every crash? So in all situations the polling is gone..

Link to post
Share on other sites

If you fear that it will crash you wrap it in a pcall

function pollEvents()
  lastRefresh = lastRefresh or 0
  pcall(function()
    local states = api.get("/refreshStates?last=" .. lastRefresh)
    if states then
      lastRefresh=states.last
      if states.events and #states.events>0 then pushEvents(states.events) end
    end
    --fibaro.emitCustomEvent('tickEvent')  -- hack because refreshState hang if no events...
    end)
end

However, this code is pretty dated by know - most of us do a http request to localhost:11111 to avoid the api.get to hang.

Link to post
Share on other sites

Hi jgab,
Do I understand that not all code can be run by the editor?
Or am I doing something wrong.

 

I'm trying to save a frequently used function in a global string, so I can use it in multiple classes.
For this I use the code below (which works with small changes in a LUA test environment).

I get the following error ''attempt to call a nil value (global 'load')'

function QuickApp:onInit()
    self:debug("onInit")

    -- create the global value with a functio in it 
    setGlobalVar = true
    self:setGlobalVariable('FunctionAdd','return function(a,b) return a+b end')

    --execute the string from the global value
    local _,code = pcall(load(api.get('/globalVariables/FunctionAdd')))
    self:debug(code(2,8))
end

Is the 'load' function not possible in the HC3?

Edited by NLWaard
Link to post
Share on other sites
3 hours ago, NLWaard said:


Do I understand that not all code can be run by the editor?

 

Is the 'load' function not possible in the HC3?

 

yes, there is set of things not available on HC3:

 

dofile=nil
getfenv=nil
getmetatable=nil
load=nil
loadfile=nil
loadstring=nil
module=nil
rawequal=nil
rawget=nil
rawset=nil
setfenv=nil
setmetatable=nil
coroutine=nil
debug=nil
file=nil
io=nil
os.execute=nil
os.getenv=nil
os.remove=nil
os.rename=nil
os.setlocale=nil
os.tmpname=nil
package=nil 

 

  • Thanks 1
Link to post
Share on other sites

Yes, some functions are not available in the HC3 (or HC2) environment. 

I do question why many of these functions are not allowed. os.execute & friend I understand but load and coroutines would make the environment so much powerful. I have heard security issues being raised but for load and coroutine it's not a valid argument. The other aspect could be to limit resource usage (memory/cpu) by limiting the Lua expressiveness but I don't buy that either  - for me it's just a bad design decision from Fibaro that at the end forces people to write bad code to get around arbitraty limitations.

 

load/loadstring is not allowed. We have the "multi-file QAs" now that allows us to include "library files" in our QA these days. Kind of statically linked libraries :-)

It does makes life easier.

I have a coding workflow where I code in ZBS, include my libraries, create the QA and debug offline in using my fibaroapiHC3.lua emulator - and with a button click build and deploy it as a multi-file QA on the HC3. The "main file" is my QA and the additional files are the library files that I include have built up during the last 6 months.

The library is the QA_toolbox

 

Whenever I start a QA project I have a template

if dofile and not hc3_emulator then
  hc3_emulator = {
    name="My QA",
    --proxy=true,
    --deploy=true,
    type="com.fibaro.deviceController",
    poll=1000,
    UI = {}
  }
  dofile("fibaroapiHC3.lua")
end

hc3_emulator.FILE("Toolbox/Toolbox_basic.lua","Toolbox")
--hc3_emulator.FILE("Toolbox/Toolbox_child.lua","Toolbox_child")
--hc3_emulator.FILE("Toolbox/Toolbox_events.lua","Toolbox_events")
--hc3_emulator.FILE("Toolbox/Toolbox_trigger.lua","Toolbox_trigger")
--hc3_emulator.FILE("Toolbox/Toolbox_files.lua","Toolbox_files")
--hc3_emulator.FILE("Toolbox/Toolbox_rpc.lua","Toolbox_rpc")
--hc3_emulator.FILE("Toolbox/Toolbox_pubsub.lua","Toolbox_pubsub")
--hc3_emulator.FILE("Toolbox/Toolbox_ui.lua","Toolbox_ui")
----------- Code -----------------------------------------------------------
_version = "0.1"
modules = {
--  "childs",
--  "events",
--  "triggers",
--  "files","rpc",
--  "pubsub",
--  "ui"
}

function QuickApp:onInit()
  self:debug("onInit",self.id)
end

where I uncomment the libraries I need for the particular QA I want to code.

For me this is the optimum way to share code and I don't really miss something I have in other coding environments... 

 

In fact there is two more modules not visible in the example above the implements a Lua interpreter in Lua and allows me to have load and coroutines and metatables on the HC3.It comes with a performance penalty but in some case the expressiveness is worth more. (I'm using it for a future rule scripting language)

 

 

 

 

 

 

 

  • Like 1
  • Thanks 1
Link to post
Share on other sites
On 9/18/2020 at 4:49 PM, jgab said:

If you fear that it will crash you wrap it in a pcall

function pollEvents()
  lastRefresh = lastRefresh or 0
  pcall(function()
    local states = api.get("/refreshStates?last=" .. lastRefresh)
    if states then
      lastRefresh=states.last
      if states.events and #states.events>0 then pushEvents(states.events) end
    end
    --fibaro.emitCustomEvent('tickEvent')  -- hack because refreshState hang if no events...
    end)
end

However, this code is pretty dated by know - most of us do a http request to localhost:11111 to avoid the api.get to hang.

 

Do you have an updated example how to use a http request to localhost:11111 ?

Link to post
Share on other sites
1 minute ago, GJ Niewenhuijse said:

 

Do you have an updated example how to use a http request to localhost:11111 ?

Have a look at the triggers module - that's the latest code I use.

 

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...