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


QuickerApp - having fun with classes...


jgab

Recommended Posts

 

This is a thread for a couple of posts on using Lua classes on the HC3 and improve the existing QuickApp class. The idea is to make a version of the QuickApp class that is a bit more helpful and feature rich than the provided. However, to get there we need to understand how to work with classes in Lua and in particular the version of OOP/classes we get in a QuickApp context.

 

Part 1: Introduction to classes on the HC3 (this post)

Part 2:

Please login or register to see this link.

...

Part 3:

Please login or register to see this link.

... (a multi-part post to develop QuickerApp...)
            Part 3.1

Please login or register to see this link.


            Part 3.2

Please login or register to see this link.

 

Introduction to classes on the HC3

(Please let me know how the text can be improved; language, clarity, examples, ...)

I also recommend the posts on the "Anatomy of QuickApps" 

Please login or register to see this link.

 

Please login or register to see this link.

 

Please login or register to see this link.

.

Please login or register to see this link.

 that takes up some of the same concepts - but a bit deeper.

 

When coding QuickApps on the HC3 we use classes - specifically one - class QuickApp.

To write a QuickApp is about extending and using that class that's provided to us by Fibaro.

What's the need to dig deeper into it?

 

Well to really leverage the QuickApp class in the best way it makes a lot of sense to understand how and why it works the way it does. Also, when we deal with QuickAppChildren, we are going to create classes ourselves.

...and there may be some other use cases for defining classes, but we will come to them.

 

Recap, coding a QuickApp in Lua means extending the QuickApp class that Fibaro provide us with.

Extending means that we can add functions (called methods in OO language) and variables to the class.

 

Please login or register to see this code.

 

When we write this we actually add a method (function) to the existing class QuickApp.

When the QA device you have coded starts up it will create an instance of the QuickApp class and

if the instance has an 'onInit' method it will be called, and off your program goes....

 

We have mentioned 3 concepts here: class, method, and instance. Let's define them better.

 

A 'class' in OOP is a template that tells us how to create 'instances' of the class. Instances are the "Objects" in OOP, but we use the word instance as it reminds us that its an object coming from a specific class.

We can have a 'Dog' class that allow us to create a set of 'Dog instances' (a whole kennel)

Coding on the HC3 we don't need dogs, so let's make an example with lights

 

A 'Light' class should be generic but know how to turn on/off a switch device with a given deviceId.

 

Please login or register to see this code.

 

Here we define the class 'Light' and then we create 2 instances of Light, l1 and l2. We see that the way to construct instances of our class is to use the class name as a function. That function Light(deviceId) that takes a deviceId to initialise the instance with, is called a 'constructor'; it constructs instances of that class.

 

To be able to turn on/off a light we can define 2 methods for that

Please login or register to see this code.

 

First  we see that the syntax for defining a method is

Please login or register to see this code.

It's like a normal Lua function definition but with <class name>: before the name of the function.

This tells Lua that the method should be "associated" to the Light class. We will go deeper into this soon.

 

Another thing we see inside the method definition of on and off is that we use the variable 'self' to get the deviceId  that we are going to turn on or off. We can now do

Please login or register to see this code.

to turn on l1 and turn off l2.

 

However, to complete this and make it work there is one missing piece - an 'initialiser'

To createe an instance, the class wants an 'initialiser' function to do the job. In our case we need to decide what to do with the deviceId (88 and 77) that is given to the constructor as an argument.

The initialiser is a special method called __init(...) and is defined like this

 

Please login or register to see this code.

 

We see that this follows the same syntax as when defining the on and off methods, but the name is  '__init'. The reason is that the name should hopefully not overlap with a name that we want to use for our own methods.

 

Here we have the 'self' variable accessible inside the initialiser again. What we do here is that we just assign self.id the deviceId that was given as an argument to the constructor.

 

A mental model (not how it really works) can be that our Light constructor is a function that takes the deviceId as the argument. Creates an empty Lua table and assign it to the variable self.

Then it calls the initialiser with self and the deviceId, the initialiser does whatever it needs to, in our case it saves the deviceId in self as the key 'id'. Then when the initialiser is finished, the constructor returns self - our new instance

 

Please login or register to see this code.

 

Well, this is a simplification. In the example we pass both self and deviceId to the initialiser. Our definition of 

Please login or register to see this code.

only takes deviceId as argument - where does ‘self’ come from?

 

It turns out that when we define a function with ':', Lua adds an "invisible" self as the first argument. In reality

Please login or register to see this code.

is translated by Lua to

Please login or register to see this code.

  

What is the '.' then? Well, in all practical ways the class 'Light' is a Lua table. A 'prototype' table that we copy to the instance when we create a new instance.

  

In Lua we can assign value to a table like

Please login or register to see this code.

and we get 

Please login or register to see this code.

Another way to achieve the same is

Please login or register to see this code.

and we also get 

Please login or register to see this code.

 

We can assign a function as value

Please login or register to see this code.

and we get

Please login or register to see this code.

We can call the function

Please login or register to see this code.

and we get

Please login or register to see this code.

 

Lua allows us to write the function assignment to a table like this

Please login or register to see this code.

and it is the same as

Please login or register to see this code.

 

In fact, all the fibaro functions you have encountered  are defined in a table named fibaro

Please login or register to see this code.

 

or written another way

Please login or register to see this code.

 

When we do this "table definition" and we use a ':' instead of a '.', Lua inserts the variable 'self' as the first argument.

We use this only for class methods and it means that we always have access to the ‘self’ variable inside methods.

When we call a method, who passes on self to the method?

We always call a method on an instance

Please login or register to see this code.

This is translated to

Please login or register to see this code.

It takes the object (l1) that we call the method on and pass it on as the first argument - that will be the 'self' parameter.

Remember that

Please login or register to see this code.

was defined/translated as

Please login or register to see this code.

 

We don't need to really understand all this but it's pretty elegant way that Lua allow us to add a syntax for OOP.

 

Does this make coding easier?

Well, maybe. Coupling functions (methods) with data (instance variables) using OOP can make code easier to work with. It gives a natural way to create abstractions to work with - and abstractions are good.

A warning here, OOP modelling can create unnecessary complexity if carried out to its extreme and I have seen projects that never finished because they got stuck designing classes for everything in the universe and beyond...

 

In the example above, all instances of light got their own instance of the self.id variable. The reason is that when Light(99) is called it makes a new empty table for self and passes that to the initialiser function. The initialiser then adds the id to the table. 

Please login or register to see this code.

 

So each instance have their own self table with instance variables - and methods. Actually, 'self' is the instance.

 

We can actually do

Please login or register to see this code.

We can add an instance variable directly to the class.

When we create a Light instance, they will start out with a ‘self’ table that contains the 'version' key. So, all class defined variables in this way will be copied to the instances.

 

If we would like to have a value that is shared between instances we can't make it just a simple value as it is copied to each instance.

In the case above each instance gets a copy of the 'version' instance variable and if we change the version of one light it is not reflected in the other lights

Please login or register to see this code.

 

We can get around that by having a table as class variable

Please login or register to see this code.

Which is not that surprising as the same table ('shared') is copied to each instance

 

This means that our instances can have a shared 'state'. It can be useful for keeping a counter across all instances or something similar.

Please login or register to see this code.

 

We could also do

Please login or register to see this code.

and it would be the same shared debug instance variable that we set, but it's better to think about shared as a "class variable" than an "instance variable"

 

Let’s  make the mental model of what a  class is a bit more advanced (still not 100% accurate).

Think about our class Light as a Lua table

Please login or register to see this code.

we define the initialiser

Please login or register to see this code.

and we get class/prototype table 

Please login or register to see this code.

we add the version  class variable

Please login or register to see this code.

and we get

Please login or register to see this code.

This is now the template for how to construct instances of Light

The constructor is called 'Light', we know that 'Light' is a Lua table. How can we call Light(66)? This is because Lua can make tables special using metatable functions that allow our table to work as a function. I will not go into it right now. I will talk about it in the next post - just trust me for now.

So, the constructor looks like

Please login or register to see this code.

 

It turns out that with this metatable thing, if the table has a '__call' key bound to a function, then if we use our table Light like this:

Please login or register to see this code.

it's translated to

Please login or register to see this code.

This is how we can use Light as both a table and a function...

 

When we define 

Please login or register to see this code.

 

We now know that we extends the QuickApp class (think table) with

Please login or register to see this code.

 

QuickApp has of course a lot more predefined methods and variables in its "table". 

Note. We can't look into the QuickApp class table (or the instance self) as they are wrapped in a special Lua type called 'userdata' that is opaque. We jus have to live with it.

 

When a QuickApp starts up it does something like

 

Please login or register to see this code.

 

It fetches the device structure for the device with the id of the QuickApp. If you go to

Please login or register to see this code.

you can see the device structure that is associated with the device/QA. The type chosen for the QA define a lot of the fields in the structure. A binary switch has a 'value' field that is true or false depending on the switch being open or closed. It also contains the name, what room the device is in, etc, etc.

 

Anyway, it passes that device structure as the argument to the QuickApp constructor. The initialiser of the QuickApp class will populate the self (the instance) with name, id, type, properties, quickAppVariables etc from the device structure.

After that it will see if the QuickApp instance created have the method ‘onInit’ defined (if the method is available in the ‘self’ table)

If so it will call the onInit method. It calls using ':' (qa:onInit()) so ‘self’ (qa) is passed on as the first argument.

 

Lastly, it actually assign the global Lua variable 'quickApp' the instance created. Note that this happens after we return from the onInit method, so 'quickApp' is not available before that.

 

However, it means that we can use quickApp in regular Lua functions to access 'self'

 

Please login or register to see this code.

 

This can make a lot of sense as we don't have to pass around self between functions - and there is only one QuickApp instance anyway. If we were juggling many QuickApp instances in our code it would make sense to pass around what 'self', what QuickApp instance, we were referring to. With only one it is always 'quickApp'

 

So, let’s introduce the next important OOP concept. 'Inheritance'.

 

The Light class we defined above is all well, but it can only turn on or off Lights. If we  have a dimmer we would like to be able to set a dim value.

 

What we can do is to 'subclass' Light and create a class 'DimLight' that besides doing all that a Light does (methods 'on' and 'off') also has a 'dim' method.

  

Please login or register to see this code.

This defines the class 'DimLight' and says that it should inherit methods and variables from class 'Light'

 

Our 'DimLight' needs an initialiser function, because we need to call the initialiser of class Light

Please login or register to see this code.

What we do in an initialiser that inherits is  that we call the initialiser function of the "parent class"

Note that we call the Light.__init function with '.' so  that we can send the self of the DimLight to the Light's initialiser.

We also pass on the deviceId to the Light's initialiser. It will store the deviceId in the self.id  for us.

 

Then we define

Please login or register to see this code.

We can create an instance

Please login or register to see this code.

We 'inherit' Light's methods and can use them too.

 

If we in our Dim class also defines the method 'on' it will 'override' the definition of on we had in the parent class. We have seen an example of this already as the the __init method of Dim overrides the __init method of Light. We can still call the parents method. In Dim we do it like

Light.__init(self,deviceId)

We call the method defined for Light but we pass our own (Dim) self.

Please login or register to see this code.

 

Here we redefine 'on' but we call the parents methods to do all the real work, and we just add a printed log message.

 

Inheritance allows us to construct a 'class hierarchy' and  create abstractions. We collect common functionality in super classes and we make specialised subclasses for objects that behave (slightly) differently from the super class.

 

In a QA we can make QuickAppChild objects.

A QuickApp can  create devices  that have deviceIds but are considered children of that (mother) QA.

If we delete the mother QA all child QAs are also deleted. 

A typical use case is to have a QA that is the controller of an external system with several devices. Ex. A 'Philips Hue Hub' QuickApp that have QuickAppChild objects that are the Hue devices connected to that Hub. (See also Fibaro's QuickAppChild documentation)

  

A QuickAppChild is a class and we can create an instance by calling

Please login or register to see this code.

However, then we will get a  generic QuickApp child that doesn't do that much. 

Typically, what we do is that we define a subclass

 

Please login or register to see this code.

Please login or register to see this code.

 

We inherit from QuickAppChild. We create an initialiser that takes 2 arguments. The QuickAppChild wants the device structure, and we want the Hue deviceID. The reason being that our Switch Child is expected to talk to its counterpart Hue device. We have a magic HueFunctions.* helper here that sends commands to the Hue...

Important here is also that the Switch updates it's properties. When a binary switch is off its value property should be false, and when it's on it should be true. This is important because then you can trigger on this child device in a Lua or block scene. Also, the in the Web UI the icon of the device changes from red to green to signal that the device is on.

  

In this post we will not get too deep into QuickAppChildren as the focus is OOP.

 

Two more concepts is worth mentioning. 'class hierarchy' and 'object hierarchy'.

When we do class inheritance we create a class hieararchy. For QuickApp it looks like

  

QuickAppBase

--> QuickApp

--> QuickAppChild

------>Switch

 

QuickAppChild doesn't inherit from QuickApp, instead QuickApp and QuickApp inherits from QuickAppBase. QuickAppBase is not a class we normally deal with but it's there. If QuickAppChild had inherited from QuickApp it would have meant that QuickAppChildren could have QhuickAppChildren... which they can't.

In the example our Switch is hanging under QuickAppChild as we inherit from that. 

So, your code will have a class hierarchy, or rather, it can have many class hierarchies as it's not neccessary to have just one class that all other classes inherits from.

 

An 'object hierarchy' usually means the way that instances relates to or "knows about" each other. Usually, it's not a strict hierarchy but rather an "object graph".

If we create QuickAppChild instances the Fibaro recommended way, they make sure that our QuickApp instance keeps a list of all it's children

It's a Lua key-value table named self.childDevices, where the key is the child's deviceId and the value is the child instance.

Please login or register to see this code.

will loop over all children and print their id and name.

Likewise, all child instances will have an instance variable self.parent that points to the QuickApp instance.

If a child wants to update a quickAppVariable in the parent it can do

Please login or register to see this code.

 

The object hierarchy or graph we get in this case is that the QuickApp points to all QuickAppChild instances using a list and each child has a link back to the QuickApp via its self.parentId variable

 

Ok, this is enough for this time, next time we are going to have more fun with classes

 

To summarise:

Defining a class that doesn't inherit from another class

Please login or register to see this code.

Defining a class that inherit from another class

Please login or register to see this code.

Defining a method 

Please login or register to see this code.

Defining an initialiser

Please login or register to see this code.

 

Edited by jgab
  • Like 11
  • Thanks 2
Link to comment
Share on other sites

  • Topic Author
  • Part2. More fun with classes - beyond QuickApp and QuickAppChild...

    (

    Please login or register to see this link.

    )

     

    This post will introduce a lot of 'class features' beyond the vanilla ones presented in the previous post.

     

    So, what can we have classes for? except for extending QuickApp and creating subclasses of QuickAppChild?

    We could define classes for some HC3 resources, like device and globalVariable. We could then make instances of these resources in our code and use them as abstractions instead of doing the fibaro.getGlobalVariable and fibaro.setGlobalVariable dance each time.

    It will require some coding but remember that we can always provide "code libraries" as extra files we add to our QA.

    If we have developed a good object abstraction of HC3 resources we can easily reuse it by dropping in our tried and trusted OOP library...

     

    Let’s start to see what we could do with fibaro global variables. The one we access with 

    Please login or register to see this code.

    We can only store strings in globals so if we want to store a Lua table we need to encode it to a string using json.encode(table)

     

    Let's wrap this into a class.

     

    Please login or register to see this code.

      

    The class is named 'Global'. There is an initialiser that stores the name of the global in an instance variable named 'name'

    Then there is a funky method defined named '__tostring'

    This is one of the special keys we can have in a class. The function bound to this one is used whenever Lua calls 'tostring' on our Global instances.

    We return the prefix "G:" to the name of our global. This way we can easily print a Global

    Please login or register to see this code.

     

    We would like to make the class a bit more advanced...

    There is another trick up the sleeve. The class function we have support property getters and setters.

    If we set an instance variable to a specific "property object" we can control the setting and getting of that instance variable.

     

    Please login or register to see this code.

     

    The functon 'property' takes 2 functions, a 'getter' method that should return the value of the property and a 'setter' value that is called when we assign a value to the property.

     

    Please login or register to see this code.

     

    This is pretty cool. Let's make our Global even more advanced and help us to automatically convert stored values

     

    Please login or register to see this code.

     

    This one checks if the variable exist, if not it creates the variable if we pass 'true' for the parameter 'create'

    The setter does an automatic json.encode of our value, and our getter try to cast the value to something useful.

    The castGlobal function looks like

    Please login or register to see this code.

    It returns Lua tables if a json string was stored, it return numbers and booleans also.

     

    Now we can do

    Please login or register to see this code.

    We could extend this with a :delete() method so we can easily remove globals we don't want.

    We could make the class smarter by caching the values retrieved with fibaro.getGlobalVariable instead of calling it every time. We would particularly save not having to do castGlobal at every access. However, if we do cache the value, we need a way to get notified if some other scene or QA change the value of the global, so we can update our cached value. One way to do that is to have an event listener that will update our global if it changes. However, this we will save for our QuickerApp class we will develop in the coming posts.

     

    We used the '__tostring' special method for our class

    Turns out we have quite a lot of these special or metatable keys... and we can define them for our classes.

    From the 5.3 documentation:

    • __add: the addition (+) operation. If any operand for an addition is not a number (nor a string coercible to a number), Lua will try to call a metamethod. First, Lua will check the first operand (even if it is valid). If that operand does not define a metamethod for __add, then Lua will check the second operand. If Lua can find a metamethod, it calls the metamethod with the two operands as arguments, and the result of the call (adjusted to one value) is the result of the operation. Otherwise, it raises an error.
    • __sub: the subtraction (-) operation. Behavior similar to the addition operation.
    • __mul: the multiplication (*) operation. Behavior similar to the addition operation.
    • __div: the division (/) operation. Behavior similar to the addition operation.
    • __mod: the modulo (%) operation. Behavior similar to the addition operation.
    • __pow: the exponentiation (^) operation. Behavior similar to the addition operation.
    • __unm: the negation (unary -) operation. Behavior similar to the addition operation.
    • __idiv: the floor division (//) operation. Behavior similar to the addition operation.
    • __band: the bitwise AND (&) operation. Behavior similar to the addition operation, except that Lua will try a metamethod if any operand is neither an integer nor a value coercible to an integer (see §3.4.3).
    • __bor: the bitwise OR (|) operation. Behavior similar to the bitwise AND operation.
    • __bxor: the bitwise exclusive OR (binary ~) operation. Behavior similar to the bitwise AND operation.
    • __bnot: the bitwise NOT (unary ~) operation. Behavior similar to the bitwise AND operation.
    • __shl: the bitwise left shift (<<) operation. Behavior similar to the bitwise AND operation.
    • __shr: the bitwise right shift (>>) operation. Behavior similar to the bitwise AND operation.
    • __concat: the concatenation (..) operation. Behavior similar to the addition operation, except that Lua will try a metamethod if any operand is neither a string nor a number (which is always coercible to a string).
    • __len: the length (#) operation. If the object is not a string, Lua will try its metamethod. If there is a metamethod, Lua calls it with the object as argument, and the result of the call (always adjusted to one value) is the result of the operation. If there is no metamethod but the object is a table, then Lua uses the table length operation (see §3.4.7). Otherwise, Lua raises an error.
    • __eq: the equal (==) operation. Behavior similar to the addition operation, except that Lua will try a metamethod only when the values being compared are either both tables or both full userdata and they are not primitively equal. The result of the call is always converted to a boolean.
    • __lt: the less than (<) operation. Behavior similar to the addition operation, except that Lua will try a metamethod only when the values being compared are neither both numbers nor both strings. The result of the call is always converted to a boolean.
    • __le: the less equal (<=) operation. Unlike other operations, the less-equal operation can use two different events. First, Lua looks for the __le metamethod in both operands, like in the less than operation. If it cannot find such a metamethod, then it will try the __lt metamethod, assuming that a <= b is equivalent to not (b < a). As with the other comparison operators, the result is always a boolean. (This use of the __lt event can be removed in future versions; it is also slower than a real __le metamethod.)
    • __call: The call operation func(args). This event happens when Lua tries to call a non-function value (that is, func is not a function). The metamethod is looked up in func. If present, the metamethod is called with func as its first argument, followed by the arguments of the original call (args). All results of the call are the result of the operation. (This is the only metamethod that allows multiple results.)

    There is actually 2 more; __index and __newindex, but they are used by the class logic itself and we can't "redefine" them

     

    This allows us to do "operator overloading" - if you been doing C++ in the past you know this...

    Anyway, because they are mostly operators, they may not be so useful as they may look at the first glimpse. Hard to know what a Global + Global would mean. However, we could take the value, if integer, and do the addition, and if a string concatenate the strings etc.

     

    Let us instead do another class, a 'Date' class.

     

    Please login or register to see this code.

     

    This allows us to create a date instance, like this

    Please login or register to see this code.

    This version fills in values from the current time in the missing fields. If we leave out the time part it will fill in the current time etc, when we create the Date instance. Another version could wait and fill in the blanks when we use the Date instance.

     

    We have a parseDate function that helps us parse the date:

    Please login or register to see this code.

     

    This function does 2 things, it parses the string date and return the time (epoch), and it returns a match function.

    This match function we can use when comparing dates

    We define the 'equality' test  for the Date class

    Please login or register to see this code.

     

    Now we can test

    Please login or register to see this code.

    A note here. There is a very good date manipulation library for Lua called

    Please login or register to see this link.

    . It uses metatables to support some date operations. It would be possible to port this to the HC3 using the class operator overloading functions instead. Maybe a project for the future... 

     

    Another example. Lets wrap Lua tables in a class

    Please login or register to see this code.

    Besides printing themselves out as json, we can also compare 2 tables and  add 2 tables together. Pretty cool. Other operations could also be defined, for intersection,  etc.

    Please login or register to see this code.

     

    Ok, that's all for this part. Imagination is the limit here.

    Edited by jgab
    • Like 3
    • Thanks 2
    Link to comment
    Share on other sites

  • Topic Author
  • Part 3. Creating a better QuickApp class - QuickerApp

     

    This will be a series of post. The idea is to develop a drop-in replacement for QuickApp class, named QuickerApp.

    The first post will be about the requirements - wanted feature of a QuickerApp class.

    I will return to this post as to refine the requirements as new ideas popup.

     

    The is a first shot from the top of my head. As the list grows it will be sorted into categories and prioritised...

    • Being able to subscribe to sourceTriggers - like scenes do.
    • Having better formatted debug printouts (structured json, html, etc) - done
    • Easier to setup timers and alarms - for times, dates and repeating
    • Easier to work with dates and times - check if time is betweeen dates etc.
    • Make the QA honour the disable checkbox for the QA. -- done
    • Better warnings if timers and json.encode crashes.
    • Better error handling, automatic notifications (sendpush,notification center on errors)
    • Easier handling of QuickAppChildren - creating and loading -- done
    • Better handling of quickAppVariables - check configuration parameters, return nil if don't exist -- done
    • Wrap devices,globals and some other resources in classes for easier handling.
    • Add missing fibaro.* functions for some resources...
    • Easier to programmatically define the QA UI and dynamically add/remove UI elements.
    • Automatic upgrade of the QA by fetching code from a repository online like GitHub (incl. optional update of the QuickerApp code itself)
    • Explicitly declare what functions should be remote callable - not like today where all QuickApp:* functions are exported -- done
    • Unified event loop for sourceTriggers and internal QA events
    • Better HTTP support
    • Separation of installation (first time run)  and restart phase of QA.
    • QA-2-QA pubsub mechanism, publish subscribe mechanism to allow QAs to find and subscribe on events from each other.
    • Self-documentation?
    • Built-in Lint?

     

    The vision is that in we can drop in the QuickerApp code in a separate QA file. Features should be optional and only load when used.

    In the main file we write:

    Please login or register to see this code.

    ...and off we go..

     

    Next post we develop the basic skeleton to replace the QuickApp class...

    Edited by jgab
    • Like 3
    • Thanks 2
    Link to comment
    Share on other sites

    fantastic 

    Please login or register to see this image.

    /monthly_2021_04/image.png.7554432ec1c699d246c1aa7836009ba8.png" /> @jgab

    • Like 2
    • Thanks 1
    Link to comment
    Share on other sites

  • Topic Author
  • So this is a first minimal version, just taking over the QuickApp class.

    Please login or register to see this code.

    Couple of things here. We start with QuickerApp:main().

    Either we have a separate install() handler or we pass an argument to main().. haven't decided yet. Probably the latter.

    Functions that should be exported, callable with fibaro.call(ID,action, ...) from another scene/QA,  is declared in the 'API namespace'.

    Same for buttons, they are declared in the UI namespace.

    This way we have control over what others can invoke, and we don't litter exports and buttons with our QuickerApp main namespace.

     

    The way we do this is by dropping in a QuickerApp file in our QA.

    Note this is the minimal start. Will add more error checks and start to replace QuickApp function with our own in the posts to come.

     

    Please login or register to see this code.

    We are going to need our own QuickerAppChild class to - with the same namespace model.

    We may be a bit more clever with the children also...

    Instead of reusing QuickApp:onInit() we could have taken over QuickApp.__init, but it's unnecessary at the moment...

    Edited by jgab
    • Like 3
    Link to comment
    Share on other sites

  • Topic Author
  • So, continuing with the QuickerApp class - our replacement for the QuickApp class

    Please login or register to see this attachment.

    Please login or register to see this attachment.

     

    Our QA using QuickerApp looks like

    Please login or register to see this code.

     

    Functions we have for QuickerApp are

    • class QuickerBase
      Base class to hold common methods for QuickerApp and QuickerChild. We don' use this directly.
    • class QuickerApp(QuickerBase)
      Our main class. The framework instantiate this for us.
    • class QuickerChild(QuickerBase)
      This we inherit from when we create child devices
    • function QuickerBase:updateView(element, typ, fmt/value, ...)
      Like QuickApp:updateView but optional format arguments
      Ex. self:updateView('label1','text',"It is %.2f degrees",fibaro.getValue(self.id,"value"))
      saves us a add a string.format...
    • function QuickerBase:updateProperty(prop, value)
      Same as QuickApp:updateProperty for now. Will make checks if property exists...
    • function QuickerBase:getVariable(name)
      Return quickAppVariable. Return nil if variable doesn't exist.
      We will improve this to do key/value lookups for quickVars.
    • function QuickerBase:setVariable(name, value)
      Same as QuickApp:setVariable(name, value)
    • function QuickerBase:debug(...)
      Same as QuickApp:debug. Will transform Lua tables to json if self._debug.json is true
      Will add optional debug level
    • function QuickerBase:trace(...)
      Same as QuickApp:trace. Will transform Lua tables to json if self._debug.json is true
      Will add optional trace level 
    • function QuickerBase:warning(...)
      Same as QuickApp:warning. Will transform Lua tables to json if self._debug.json is true
      Will add optional notifications
    • function QuickerBase:error(...)
      Same as QuickApp:error. Will transform Lua tables to json if self._debug.json is true
      Will add optional notifications
    • function QuickerBase:debugf(fmt,...)
      Like debug  but with string.format arguments
    • function QuickerBase:tracef(fmt,...)
      Like trace  but with string.format arguments
    • function QuickerBase:warningf(fmt,...)
      Like warning  but with string.format arguments
    • function QuickerBase:errorf(fmt,...)
      Like error but with string.format arguments
    • function QuickerApp:createChild({
      className = <class>,  -- Name of our QuickerChild class
      name = <name>,
      type = <type>,
      quickVars = <table>,    -- Key/Value table that will be moved to properties.quickAppVariables
      properties = <table>,
      interfaces = <table>
      })
      This is the way to create children. The difference is that we pass along the class name of the child. It will be stored and used when loading children.
    • function QuickerApp:loadChildren()
      Our way of loading existing children. Will return self.childDevices and the number of children loaded.

    We have a shared debug flags table shared between QuickerApp and QuickerChild

    Please login or register to see this code.

    We can at any time change it.

    Please login or register to see this code.

    We will add  more debug flags as we extend the class.

     

    The normal QuickApp model when it comes to handling UI events are a bit ..."ugly".

    The built-in UIs we get with some device types call onAction directly instead of going over UIEvent.

    The idea is probably that we should be able to have a function

    function QuickApp:turnOn() ...end

    that is both callable with fibaro.call(ID,"turnOn")

    ...and that we could "reuse" the same function for the function we specify in the Control form's 'onReleased' field.

    The problem is that in the latter case the turnOn function gets some extra arguments that we ignore, so in reality, it's
    not a big problem. However, in practice, it should be 2 different functions.

    We have divided our functions between UI and API. To be strict we should define turnOn both for UI and API, however,

    to make it easier we search for the UIEvent function in API too... so it works in a similar way.

     

    Most functions in QuickerApp we define from scratch and don't reuse the same function from QuickApp. The reason is that we

    get better control over what we should do and can add our own error handling as we go along. self:setVariable still uses 
    QuickApp:setVariable but we will change that as we improve on that function too

     

    Please login or register to see this attachment.

    Please login or register to see this attachment.

    Edited by jgab
    • Thanks 2
    Link to comment
    Share on other sites

  • Topic Author
  • Another play with classes.

    The HC3 console log supports some limited HTML formatting.

    HTML tables and lists are supported.

    Here are 2 classes for this

    Please login or register to see this code.

    Which gives a log

    Please login or register to see this image.

    /monthly_2021_04/tables.png.aec89a1c5645c01daf054ce9e387205d.png" />

    We can update the Lua table (self.tab) at any time and print it out when needed.

    Because they leverage the __tostring trick, we can easily nest tables and list.

    This could be improved with the limited HTML color support there is.

    Edited by jgab
    • Like 1
    Link to comment
    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...