Jump to content

Welcome to Smart Home Forum by FIBARO

Dear Guest,

 

as you can notice parts of Smart Home Forum by FIBARO is not available for you. You have to register in order to view all content and post in our community. Don't worry! Registration is a simple free process that requires minimal information for you to sign up. Become a part of of Smart Home Forum by FIBARO by creating an account.

 

As a member you can:

  •     Start new topics and reply to others
  •     Follow topics and users to get email updates
  •     Get your own profile page and make new friends
  •     Send personal messages
  •     ... and learn a lot about our system!

 

Regards,

Smart Home Forum by FIBARO Team


Recommended Posts

Posted (edited)

This is  a easy and powerful way of creating QuickAppChildren.
Repo: 

Please login or register to see this link.

 

The advantages of using the library is:
1. It's a reusable library reducing the amount of code needed to load/init/create children.

2. It has it's own child loader allowing to have different QuickAppChild classes mapped to the same fiber device type (not allowed by Fibaro's standard implementation)

3. Support for template definition of custom child UIs

4. Supports different children management models. "Static" definition of a fixed set of children, "dynamic" creation of children at runtime. "Syncing" of children against a table of external devices, or a mix of all of the above.

5. Is there is any problem with the code you can complain to me and I will fix it :-) 

 

A central concept is that all children created has a unique identifier - uid. That can be any string but must be unique among the children for the QA. (children belonging to other QAs don't matter).
This allows us to know if a child exists or needs to be created, or is no longer managed by the QA and should be removed.

If you have a fixed set of children you typically have a role for them and can give them ex. uids, 'temp1', 'humidity1', 'lux1'.

his will allow you to address them as ex. self.temp1.updateProperty('value',23) from your QA.

If you have a dynamic set of children, ex. external devices on a Hue bridge, I suggest using the uid of the Hue device as the uid for the QA child. It will help a lot when new data from the bridge arrives and need to be sent to the QA child waning the data... etc.
 

Usage:

Alt1. Create children from initialization table

Please login or register to see this code.

Will load existing children defined in table.
Will create missing children defined in table but not loaded.
Will remove existing children not defined in table.

Alt2. Load existing children and create new children "manually"

Please login or register to see this code.


So, at startup, you either decide what children you are going to have and create the initialization table, and then call self:initChildren(children).
The table can have static children (children that should always be created), but one could also add children by scanning for external devices or reading user preferences what children should be created.
At the end we have a table of all children there should be, and we let  self:initChildren(children) do all the work.
The way to change the number of children is to restart the  QA so the initialization table will be recalculated and self:initChildren(children)  is called again.
This also means that if a user delete a child in the web UI, the child will be recreated when the QA starts up if it's in your initialization table.

Alternatively, at startup you load already created children with self:loadExistingChildren() and then dynamically creates children when ex. a user requests it by some action, ex. by clicking a button. If the user deletes a child in the web UI it will not be automatically re-created by self:loadExistingChildren() - because it doesn't exist anymore....

The uid is an unique identifier defining this child. Two children with the same uid can't be created.
self:createChild(uid,...) with the uid of an existing child, will delete the existing child and create a new with the same uid (but it will get a new QA deviceId).
After QA startup, self.childDevices is a table mapping QA deviceIds to Lua child objects.
self.children is a table mapping uids to child objects.

.store is an optional parameter that will store the key/values as internalStorage keys in the child QA. It can be a good place to store child specific parameters if you don't want to pollute the quickAppVariables of the child.

.room is an optional integer being the roomID the child QA should be placed in.

UI is an optional UI definition table, creating custom UI elements for the child QA.
Ex.

Please login or register to see this code.

Each entry is one line in the UI.
To place several items on the same line group them in a list:
 

Please login or register to see this code.

This makes it possible to place a label before a button on the same line in the UI. Something that is not possible with the Web UI designer.

Buttons and switches also supports onLongPressed etc.
 

Please login or register to see this code.

select also takes options={...} and value=<val> arguments. multi takes options={...} and values={...}.

slider takes min,max, and step arguments.

 

When creating children for external devices, it can be wise to chose the uid to be the uid of the external device. Ex. Hue devices have uids, and most systems have uids.

When creating a fixed set of children for ex. two temperature sensors, we can ex. give them the uid 'temp1' and 'temp2'. Then when the children are loaded we can refer to them as (ex. calling an update method on them:

Please login or register to see this code.


Classes for children inhereits from QwikAppChild:

Please login or register to see this code.


Children will have two QwikAppChild specific instance variables defined

Please login or register to see this code.


If you have stored something in the internalStorage, you can pick it up in the constructor.

Please login or register to see this code.

 

Edited by jgab
  • Like 7
  • Topic Author
  • Posted

    Example:
     

    Please login or register to see this code.

     

    This QA creates 2 children, ChildA and ChildB.

    Please login or register to see this image.

    /monthly_2025_01/image.png.be5bef5af7fa73c9cdc7d77235412d0e.png" />image.png.c61ba4a91b9144f6ae0b4d29cc758a3d.png

     

    The main QA updates the children every 5s with some random values.

    The strategy is that the data is sent to both children and they pick out their own data (the A key response. the B key of the data table).
     

    The children also communicate with each other through
    self.parent.children.childA

    resp.

    self.parent.children.childB

    This way they can update properties an UI elements in the other children.

    When we drag the slider in childB it will update the value property of childA.

    • Like 1
    • 3 weeks later...
    Posted (edited)

    Please login or register to see this code.

       @jgabstore={x='1'} was not executed,now i use:
     

    Please login or register to see this code.

    Please login or register to see this code.

     

    Edited by lux
    Posted

    @jgab I have been looking at this to refactor my lifx lamps controller. I previously used fibaroExtra (where I was only using the QuickerAppChild functions) and functions from your older toolbox.ui.

     

    This qwikappchild code is nicely self contained but one feature which seems to be missing is the child removed hook. This is useful as it allows me to clean up all my lifx device context when a child is removed. Is there another way of achieveing this with qwikappchild?

  • Topic Author
  • Posted

    Ok, I have back ported it to the new lib

    Please login or register to see this link.

     

    Posted (edited)

    Thanks. If I want to delete a child device from within my code what do I need to reset to keep qwikappchild consistent? I see there is a removeChildDevice function (or calling api.delete("/plugins/removeChildDevice/" .. childID) ?? ) but do I need to update any QwickAppChild tables such as :   self.childDevices[childID] = nil    self.allchildren[uid] = nil     self.children[uid] = nil  ?

     

    Edited by James R
  • Topic Author
  • Posted
    On 9/28/2025 at 9:06 PM, James R said:

    Thanks. If I want to delete a child device from within my code what do I need to reset to keep qwikappchild consistent? I see there is a removeChildDevice function (or calling api.delete("/plugins/removeChildDevice/" .. childID) ?? ) but do I need to update any QwickAppChild tables such as :   self.childDevices[childID] = nil    self.allchildren[uid] = nil     self.children[uid] = nil  ?

    Yes, you can use QuickApp:removeChildDevice(id)
    If you are using the self.children[uid] to get references to the children, that table is not updated by removeChildDevice.

    It's not a big issue, but if you use the self.children as the "truth" what children are up you may need to nil the entry for the child you delete.

    The table is rebuilt every time the QA restart, so it can make sense to restart the QA (plugin.restart()) when you delete a child.

     

    QwikappChild itself is designed so that the registered child itself contains what class it is etc, so it should be self-contained and able to restore themselves when the QA starts/restart.

     

    A use case is that you have a stored list with the children there should be and QwikAppchild code takes care of adding and removing children to match that list (e.g. using self:initChildren(children) )
    In that case you need to store that list somewhere and update the list  when you add/remove children. Then when the QA restarts, you read in the list and give it to self:initChildren(children).

     

    Another use case is that you just create and delete children from your own code, and when the QA restarts, call self:loadExistingChildren() to set them up.

    Posted

    I am creating the children from my own code as all the context for the external devices (Lifx lamps) is also held in the master table in the code. To restore the children after a QA restart I needed to call self:loadExistingChildren() with a nil parameter, this caused an error (table expected as there is an assert statement in function). If I call with an empty table then it deleted all the existing children. I commented out the assert in the code to get this to work.

     

    I'm trying to convert from FibaroExtra plus the older Toolbox.ui functions to QwikAppChild

     

    I gett all the children created and all the standard features of the device types work but I'm not getting the UI attached to the device. In swagger I can see the callbacks but uiview is an empty table. There must be something wrong with my format for the UI but I'm struggling to see it. My table for the UI comes from a function which varies by device capability:

     

    Please login or register to see this code.

     and if I print the uiView and uiCallbacks tables in createChild I get:

     

    [03.10.2025] [12:10:36] [DEBUG] [QUICKAPP956]: { [1] = { ['style'] = { ['weight'] = '1.0' }, ['type'] = 'horizontal', ['components'] = { [1] = { ['style'] = { ['weight'] = '1.0' }, ['name'] = 'labelF', ['type'] = 'label', ['visible'] = true, ['text'] = 'Fade Time' } } }, [2] = { ['style'] = { ['weight'] = '1.0' }, ['type'] = 'horizontal', ['components'] = { [1] = { ['style'] = { ['weight'] = '1.0' }, ['eventBinding'] = { ['onChanged'] = { [1] = { ['params'] = { ['actionName'] = 'UIAction', ['args'] = { [1] = 'onChanged', [2] = 'fadeTime', [3] = '$event.value' } }, ['type'] = 'deviceAction' } } }, ['name'] = 'fadeTime', ['type'] = 'slider', ['visible'] = true, ['text'] = 'Fade Time' } } }, [3] = { ['style'] = { ['weight'] = '1.0' }, ['type'] = 'horizontal', ['components'] = { [1] = { ['style'] = { ['weight'] = '1.0' }, ['name'] = 'labelS', ['type'] = 'label', ['visible'] = true, ['text'] = 'Saturation' } } }, [4] = { ['style'] = { ['weight'] = '1.0' }, ['type'] = 'horizontal', ['components'] = { [1] = { ['style'] = { ['weight'] = '1.0' }, ['min'] = 0, ['eventBinding'] = { ['onChanged'] = { [1] = { ['params'] = { ['actionName'] = 'UIAction', ['args'] = { [1] = 'onChanged', [2] = 'Saturation', [3] = '$event.value' } }, ['type'] = 'deviceAction' } } }, ['max'] = 65535, ['type'] = 'slider', ['visible'] = true, ['name'] = 'Saturation', ['text'] = 'Saturation' } } }, [5] = { ['style'] = { ['weight'] = '1.0' }, ['type'] = 'horizontal', ['components'] = { [1] = { ['style'] = { ['weight'] = '1.0' }, ['name'] = 'labelK', ['type'] = 'label', ['visible'] = true, ['text'] = 'Kelvin' } } }, [6] = { ['style'] = { ['weight'] = '1.0' }, ['type'] = 'horizontal', ['components'] = { [1] = { ['style'] = { ['weight'] = '1.0' }, ['min'] = 2500, ['eventBinding'] = { ['onChanged'] = { [1] = { ['params'] = { ['actionName'] = 'UIAction', ['args'] = { [1] = 'onChanged', [2] = 'Kelvin', [3] = '$event.value' } }, ['type'] = 'deviceAction' } } }, ['max'] = 9000, ['type'] = 'slider', ['visible'] = true, ['name'] = 'Kelvin', ['text'] = 'Kelvin' } } } }
    [03.10.2025] [12:10:36] [DEBUG] [QUICKAPP956]: NEW CHILD CBS
    [03.10.2025] [12:10:36] [DEBUG] [QUICKAPP956]: { [1] = { ['name'] = 'fadeTime', ['callback'] = 'parseValueFade', ['eventType'] = 'onChanged' }, [2] = { ['name'] = 'Saturation', ['callback'] = 'parseValueSaturation', ['eventType'] = 'onChanged' }, [3] = { ['name'] = 'Kelvin', ['callback'] = 'parseValueKelvin', ['eventType'] = 'onChanged' } }

     

    There UI elements seem to be there but I can't see what's incorrect/missing which means the UI is not added to the child device. 

    Posted

    If I inspect the created child device using swagger there is no uiView property data and the child only has the standard colorComponents controls. (I do get the extra UI with the FibaroExtra toolbox.ui approach).

     

    If I PUT the UI definition (Modify Device) in swagger. I do see the UI components in the device when inspected with swagger but get no UI at all - not even the standard controls!

     

    Looking at the device that works (previous approach) the UI component data is also in the property "viewLayout" but I expect that has been created as part of the "normal" child creation process.  So, it's back to working out what is wrong with my data for the create call.

     

    This is my props data sent to api.post("/plugins/createChildDevice", props)

     

    {"name":"Floor","initialInterfaces":["quickApp","quickAppChild"],"type":"com.fibaro.colorController","parentId":956,"initialProperties":{"uiCallbacks":[{"eventType":"onChanged","callback":"parseValueFade","name":"fadeTime"},{"eventType":"onChanged","callback":"parseValueSaturation","name":"Saturation"},{"eventType":"onChanged","callback":"parseValueKelvin","name":"Kelvin"}],"uiView":[{"components":[{"name":"labelF","style":{"weight":"1.0"},"visible":true,"type":"label","text":"Fade Time"}],"style":{"weight":"1.0"},"type":"horizontal"},{"components":[{"name":"fadeTime","style":{"weight":"1.0"},"visible":true,"type":"slider","text":"Fade Time","eventBinding":{"onChanged":[{"params":{"actionName":"UIAction","args":["onChanged","fadeTime","$event.value"]},"type":"deviceAction"}]}}],"style":{"weight":"1.0"},"type":"horizontal"},{"components":[{"name":"labelS","style":{"weight":"1.0"},"visible":true,"type":"label","text":"Saturation"}],"style":{"weight":"1.0"},"type":"horizontal"},{"components":[{"name":"Saturation","style":{"weight":"1.0"},"visible":true,"type":"slider","max":65535,"min":0,"text":"Saturation","eventBinding":{"onChanged":[{"params":{"actionName":"UIAction","args":["onChanged","Saturation","$event.value"]},"type":"deviceAction"}]}}],"style":{"weight":"1.0"},"type":"horizontal"},{"components":[{"name":"labelK","style":{"weight":"1.0"},"visible":true,"type":"label","text":"Kelvin"}],"style":{"weight":"1.0"},"type":"horizontal"},{"components":[{"name":"Kelvin","style":{"weight":"1.0"},"visible":true,"type":"slider","max":9000,"min":2500,"text":"Kelvin","eventBinding":{"onChanged":[{"params":{"actionName":"UIAction","args":["onChanged","Kelvin","$event.value"]},"type":"deviceAction"}]}}],"style":{"weight":"1.0"},"type":"horizontal"}]}}

     

    @jgab,, can you see anything wrong with this data?

  • Topic Author
  • Posted

     

    Ok, it was a bug to assert that out needed to be a table. I have fixed it in 2.5.1.

     

    The problem with your UI is probably the min/max you give to the slider. They need to be strings (min = tostring(lower)). That's the way the fiber api accepts the parameter.

    If any UI data is not accepted by the fibaro api, the whole ui becomes empty...

     

    I've been looking at the QwikAppChild lib and I will update it under the weeks that comes. I will keep the current functionality, but add better error messages and examples. Expect a 3.0 soon.

    So,  typically what I do when creating children for Hue devices, or other system devices, is that my code get the devices from Hue (in this example), and then creates a childrenTable that I can give to :initChildren.
    The uid is the uid in the Hue system.

    This means that whenever the QA is restarted, :initChildren with a new fresh childrenTable will remove children for Hue devices that don't exist anymore, create new children for new Hue devices the user added, and load existing children for Hue devices that been there since the last restart, - e.g. keeps things in sync.

     

    Of course you can call :loadExistingChildren, but then you need to have a mechanism to remove deleted LifX devices and create new LifX devices.

     

    To allow the user to decide what devices to add, the flow looks like (hue example).
    1. childrenTable is saved table or {}

    2. allDevices is look up of all devices on Hue. 

    3. remove all children in childrenTable not in allDevices (prune list from devices removed from Hue bridge)
    2. self:initChildren(childrenTable) (syncs children with hue devices)
    4. Using allDevices, create multi switch selector, with devices in childrenTable checked, and devices not in childrenTable unchecked.
    5. If user selects new devices in multi switch selector, add those to the childrenTable and save the table, and restart the QA

    Posted (edited)

    Thanks.

     

    I have set the slider values to strings, here are the props:

    {"parentId":956,"initialInterfaces":["quickApp","quickAppChild"],"initialProperties":{"uiView":[{"components":[{"visible":true,"text":"Fade Time","name":"labelF","type":"label","style":{"weight":"1.0"}}],"type":"horizontal","style":{"weight":"1.0"}},{"components":[{"visible":true,"eventBinding":{"onChanged":[{"type":"deviceAction","params":{"actionName":"UIAction","args":["onChanged","fadeTime","$event.value"]}}]},"text":"Fade Time","name":"fadeTime","type":"slider","style":{"weight":"1.0"}}],"type":"horizontal","style":{"weight":"1.0"}},{"components":[{"visible":true,"text":"Saturation","name":"labelS","type":"label","style":{"weight":"1.0"}}],"type":"horizontal","style":{"weight":"1.0"}},{"components":[{"visible":true,"min":"0","eventBinding":{"onChanged":[{"type":"deviceAction","params":{"actionName":"UIAction","args":["onChanged","Saturation","$event.value"]}}]},"text":"Saturation","name":"Saturation","max":"65535","type":"slider","style":{"weight":"1.0"}}],"type":"horizontal","style":{"weight":"1.0"}},{"components":[{"visible":true,"text":"Kelvin","name":"labelK","type":"label","style":{"weight":"1.0"}}],"type":"horizontal","style":{"weight":"1.0"}},{"components":[{"visible":true,"min":"2500","eventBinding":{"onChanged":[{"type":"deviceAction","params":{"actionName":"UIAction","args":["onChanged","Kelvin","$event.value"]}}]},"text":"Kelvin","name":"Kelvin","max":"9000","type":"slider","style":{"weight":"1.0"}}],"type":"horizontal","style":{"weight":"1.0"}}],"uiCallbacks":[{"callback":"parseValueFade","eventType":"onChanged","name":"fadeTime"},{"callback":"parseValueSaturation","eventType":"onChanged","name":"Saturation"},{"callback":"parseValueKelvin","eventType":"onChanged","name":"Kelvin"}]},"type":"com.fibaro.colorController","name":"Floor"}

     

    still no UI and inspecting the device still showing an empty ui table:

     

    "uiCallbacks": [ { "callback": "uifadeTimeOnReleased", "eventType": "onReleased", "name": "fadeTime" }, { "callback": "uiSaturationOnReleased", "eventType": "onReleased", "name": "Saturation" }, { "callback": "uiKelvinOnReleased", "eventType": "onReleased", "name": "Kelvin" } ], "uiView": [],

     

    My flow for managing the bulbs is:

    1. when QA starts load/attach existing children - loadExistingChildren

    2. initiate a discovery - there is no hub for Lifx devices so they can appear at any time - I still need to use the Lifx app to onboard a new device.

    3. add any new devices - createChild

    4. If, through discovery, I don't see a device that is an existing child I hide it as the problem is usually that someone has turned off a lamp at the physical switch. When it comes back I make it visible

    5. If the device has not been seen for a very long time then the child can be deleted

    Edited by James R
  • Topic Author
  • Posted

    The props are a bit difficult to read...  and I can't test with it.

    Can you give me the UI definition table that don't work. I guess it's the buildUI variable you create.

    I see in an earlier post that you say

    Please login or register to see this code.

    You mean it's the props that QwikAppChild sends? Worried hat you did your own api.post 😅

    Posted (edited)

    No, I'm not doing the api posy, just getting it from this by adding a line to _createChildDevice

     

        local device, res = api.post("/plugins/createChildDevice", props)

        print("CREATE CHILD RESULT: " .. tostring(res) .. " - " .. json.encode(props))

     

    Yes, the definitions are as produced by the buildUI function

     

    input = {

      {label='labelF', text='Fade Time'},

      {slider='fadeTime', text='Fade Time', onChanged='parseValueFade'},

      {label='labelS', text='Saturation'},

      {slider='Saturation', text='Saturation', onChanged='parseValueSaturation', min="0", max="65535"},

      {label='labelK', text='Kelvin'},

      {slider='Kelvin', text='Kelvin', onChanged='parseValueKelvin', min="2700", max="6500"}

    }

     

    device is a com.fibaro.colorController and is set by:

     

    function addNewChild(k)

        local childUI = getChildUI(k)

        local dName = LxD[k].label

        local dType = getChildType(k)

        local props = {

            name = dName,

            type = dType,

            interfaces = {'quickApp'},

        }

        dev = myQA:createChild(k, props, 'LifxLamp', childUI)

        if dev then

            myQA:trace("LifxTrace: setting new device ", k, " childref to ", dev.id, " name ", LxD[k].label, "type: ", dType)

            LxD[k].childRef = dev.id

        else

            print("device creation failed for:",  k, LxD[k].label)

        end

    end

     

    LxD is my master device table which is built from discovery responses

    Edited by James R
  • Topic Author
  • Posted

    You can't set interface 

    Please login or register to see this code.

    It will result in an empty UI.

    Children are of type 'quickAppChild' and is set by the api.

    There used to be a hack in the past to set the child's interface to 'quickApp' to allow us to set the viewLayout to get custom UI, but that seems not to be compatible with the new (approved) uiView approach.

    Posted

    That's it !!!    Thank you so much for your help. I guess that is a remnant of the old hack that I used to get it to work before.

    • 4 weeks later...
    Posted

    Hello, @jgab I have a generic question, I was looking all over the place but could not find a list of all the fibaro device types (com.fibaro.*).
    Do you know where I can find a list like this? Thanks. 

  • Topic Author
  • Posted
    34 minutes ago, BMXsanko said:

    Hello, @jgab I have a generic question, I was looking all over the place but could not find a list of all the fibaro device types (com.fibaro.*).
    Do you know where I can find a list like this? Thanks. 

    Please login or register to see this attachment.

    • Like 1
  • Topic Author
  • Posted (edited)

    or a simple tree

    Please login or register to see this code.

     

    Edited by jgab
    • Like 1

    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...