Hasp["1"].page[0].tab[0].btn[0].value_str - too complex or run of the mill?

In my quest to create an "easy" :stuck_out_tongue_winking_eye: way to configure the page layouts for openHasp, I thought using a function node would be a good idea, as it takes advantage of the Monaco syntax checking.

Chunks of code can be copied only requiring a couple of values to be edited to suit.
(anyone else always end up with far too many brackets when copy / pasting :joy:

I have used arrays so that there is no need to enter numbers for the pages, tabs and buttons, as these can now be calculated, based on the order they are listed.

So if you take a look at this sample, do you think it would be easy enough to understand - how to add a new "hasp", page, tab, and button ?

If I stick with this I will write up some instructions and better comments

But would be interested in any suggestions please ? @yogy @stefan24 @BartButenaers Anyone ?

EDIT - added page setup to allow for size etc to be configured. If not provided it uses some defaults, I think I will move the defaults into the theme object, next.

const layout = {

    "1": { // name of hasp device (topic)

        page: [ //***************** start of pages ******************

            {   // ********* page 1 start **********
                props: { width: 480, height: 308, minGap: 15, topMargin: 20, bottomMargin: 15, leftMargin: 0, rightMargin: 0 }, // Page Settings
                tab: [
                    {  // *************** First tab start *******************
                        props: { name: "Lounge", btnStyle: "btntoggle" }, // Tab 0 Settings 
                        btn: [
                            { text: "\n", value_str: "Main", extra: { topic: "loungelights/POWER2", type: "Tas" } },
                            { text: "\n", value_str: "Lamp", extra: { topic: "Bulb-20/POWER1", type: "Tas", "color": true, name: "lounge_lamp" } },
                            { text: "\n", value_str: "Wall", extra: { topic: "loungelights/POWER1", type: "Tas" } },
                            { text: "\n", value_str: "Dining", extra: { topic: "Bulb-10/POWER1", type: "Tas", "color": true, name: "dining_lamp" } },
                            { text: "\n", value_str: "Fire", extra: { topic: "lounge_fire", type: "RF433" } },
                            { text: "\n", value_str: "Fish Pump", extra: { topic: "fish_pump", type: "RF433" } }
                        ]
                    }, // *************** First tab end *******************
                    {
                        props: { name: "Scene", btnStyle: "btnscene" }, // Add the properties to Tab 1
                        btn: [
                            { text: "\uF1E1\n", value_str: "Main", extra: { topic: "scene/lounge/1", type: "Tas" } },
                            { text: "\uE8DD\n", value_str: "Lamp", extra: { topic: "scene/lounge/2", type: "Tas" } },
                            { text: "\uE6B5\n", value_str: "Dining", extra: { topic: "scene/lounge/3", type: "Tas" } },
                            { text: "\uE238\n", value_str: "Fire", extra: { topic: "scene/lounge/4", type: "RF433" } },
                             { text: "\uF1E1\n", value_str: "Main", extra: { topic: "scene/lounge/1", type: "Tas" } },
                            { text: "\uE8DD\n", value_str: "Lamp", extra: { topic: "scene/lounge/2", type: "Tas" } },
                            { text: "\uE6B5\n", value_str: "Dining", extra: { topic: "scene/lounge/3", type: "Tas" } },
                            { text: "\uE238\n", value_str: "Fire", extra: { topic: "scene/lounge/4", type: "RF433" } },
                             { text: "\uE238\n", value_str: "Fire", extra: { topic: "scene/lounge/4", type: "RF433" } }

                        ]
                    },
                    {
                        props: { name: "Blinds", btnStyle: "btnbutton" }, // Add the properties to Tab 2
                        btn: [
                            { text: "\uF1E1\n", value_str: "Main", extra: { topic: "blinds/lounge/1", type: "Tas" } },
                            { text: "\uE8DD\n", value_str: "Lamp", extra: { topic: "blinds/lounge/2", type: "Tas" } },
                            { text: "\uE6B5\n", value_str: "Dining", extra: { topic: "blinds/lounge/3", type: "Tas" } },
                            { text: "\uE238\n", value_str: "Fire", extra: { topic: "blinds/lounge/4", type: "RF433" } }

                        ]
                    }
                ]
            }, // ************ page 1 end ***********

            {  //  page 2 start  
                tab: [
                    {
                        props: { name: "Kitchen", btnStyle: "btntoggle" },// Add the properties to Tab 0
                        btn: [
                            { text: "\uE769\n", value_str: "Main", extra: { topic: "utility/POWER1", type: "Tas" } },
                            { text: "\uE374\n", value_str: "Counter", extra: { topic: "kitchen-lightstrip/POWER1", type: "Tas" } },
                            { text: "\uF2BA\n", value_str: "Cupboard", extra: { topic: "kitchen-lightstrip/POWER3", type: "Tas", color: true, name: "kitchen_cupboard_light" } },
                            { text: "\uE210\n", value_str: "Fan", extra: { topic: "kitchenfan/POWER1", type: "Tas" } },
                            { text: "\uE769\n", value_str: "Utility", extra: { topic: "utility/POWER2", type: "Tas" } },
                            { text: "\uF020\n", value_str: "Outside", extra: { topic: "outsidelights/POWER1", type: "Tas" } }
                        ]
                    },
                    {
                        props: { name: "Extra", btnStyle: "btntoggle" },// Add the properties to Tab 1
                        btn: [
                            { text: "\uF1E1\n", value_str: "Main", extra: { topic: "scene/kitchen/1", type: "Tas" } },
                            { text: "\uE8DD\n", value_str: "Lamp", extra: { topic: "scene/kitchen/2", type: "Tas" } },
                           

                        ]
                    }
                ]

            } // page 2 end

        ] //***************** end of pages ******************
    }
}//***************** end of hasp device names ******************

I think that for a novice this sample is good to follow.
If there are also are some function nodes to create a kind of template for the complete screen as seen in an earlier post of you ,with the time etc.
Your earlier example with all the color full buttons I had to change the inject node to my screen size (800x480) but also in the function node.
I think that the first choice is page layout, then the button layout per page.
But how about sliders and different format objects.
This is a start for (D)haspboard v1.0 :joy:

@yogy Thanks for looking. Obviously I cannot easily test any other screen sizes, so perhaps you can show me how it looks on your screen and what you needed to change. It should be possible to make this more easily configured.

This is an attempt to allow minimum effort to configure, what you might call "standard" similar pages, since in my case I want a page per room with buttons for the devices in that room.

To add some more flexibility I added the tabs option. There can be as many tabs as you like on each page screen space allowing. These can have x number of additional buttons, and the style of button for each tab can be specified.

I created a theme object which defines a few different button styles etc, which can be specified in the layout file, so by setting a style to the button all the nice formatting etc is inserted automatically.

I have modified that layout a bit since posting, and completely revamped the function to create the jsonl from it. (I have added an extra property to store any addition info related to each button, in my case this is the type of device it is controlling and the topic to send the command to, as this makes the logic for routing button presses and actually controlling the devices much easier.)

Based on the layout the function generates the jsonl for each page.
It counts the number pages tabs and buttons to create the sequential id numbers, then arranges the buttons in a sensible number of rows and columns.

Based on the specified container size the buttons are scaled to fit within the space. I have also scaled the icons and text along with some of the other adjustable values used in my theme. (I will probably have to add for all values that might need to be scales at some point)

That is my intention, since I cannot have PCs all over the house :wink:

I don't plan to have any sliders etc on my main pages, I have a colour popup that is only shown if you long press the button of a colour capable device :wink:

I also have a pin pad widget, this also pops up as required.

As time allows I guess I could move the styling of these and any other widgets I create, in to the theme as well.....

Popups are also "on the fly" constructed and deconstructed from node-red?
That would make various page layouts not necessary indeed.

Yes that's the idea!

I need to make it user friendly as I plan to install these in place of existing light switches, so they should "look" and work like a light switch at the basic level.

Based on the layout file above, some example pages, so its easier to visualise how it works based on that minimal data. (NOTE everything in the extra properties is not required for the layout)

First tab is the name of the room and will be the "home" page for the hasp in that room.
image

optional tabs with different button style, note the layout is automagically created based on number of buttons :rofl:
image image

Different page with only 2 tabs.
image

1 Like

With your unaltered flow it looks like this

When I change the values in the inject to 800 & 480
Looks like this

When I change in the function the tabviewValues to w 800 h 480

const tabviewValues = {
    "id": 50, "obj": "tabview", "btn_pos": 1, "y": 75, "w": 480, "h": 353, "bg_color": backgroundColor, "text_font": 26, "border_side": 0,

is this the result

There is on al the formats an unused upper space

If you want it to the top of the screen then y=0

Done that, ok.
I understand that you can use it for page 0, ,is there a provision for the underside of the screen?

The column count works ok tried with 3 / 4 / 5 / 6.
But whatever value for the rows, they are cutoff on the underside.
So the row calculation is not there yet.
And I notice left and under a grey line.

@yogy Thanks for testing - Its dependent on what values you are sending to the function. That code uses a list of "dummy" buttons to demonstrate calculating the coordinates and sizes of different arrangements of buttons.

This topic is about the next step, which is about a way to define actual content for each page, in a way so that arrangement is done automatically.

If you can show what you have in the inject node and this line of the function, but reply to the other topic please, then I can take a look for you :smiley:

const tabviewValues = {
"id": 50, "obj": "tabview", "btn_pos": 1, "y": 75, "w": 480, "h": 353, "bg_color": backgroundColor, "text_font": 26, "border_side": 0,

Hi @smcgann99,
Sorry for not responding earlier!! Have quite some backlog...

But I think you have made good progress: although I only had time to have a quick look at your proposal, it was immediately clear how it works. Somethink like this would lower the threshold a lot, to get started with this.

Could you explain please a bit more the entire setup. You have this function node to specify the content of the screens, and you have somewhere a theme object to style you widgets, e.g. the btntoggle. You also calculate the layout based on the number of buttons, and you generate a jsonl from all of this. Is it a large number of separate nodes that is used to do all of this, or a single function node?

And yes indeed the name dhaspboard also gets my vote :wink:

BTW thanks a lot for all the time you have spend on this, to smoothen the path for us to get started with this!

1 Like

Bart,

Nice to have you back :wink:

I have the code in logical groups at the moment, it's an evolution from that I shared with you some time ago. But basically there is a function node for each main part yes. I thought about creating a UI, but in the end its a lot quicker to copy paste the layout in a function node, and make a few edits.

1 Like

Very nice! I love it. Much more easier to get started!

Have no time yet to look into full detail, but had a quick look. I think it works like this?

Some high level questions:

  1. Where is the "mapping" variable (from flow memory) being used? I don't see it at first sight...

  2. Am I correct that the theme setup is completely independent from the pages layout, so this might be visually a bit more easy to see if you don't put them into into a chain:

    image

    Otherwise it looks like you send a message containing a pages layout to the theme setup node, that depends on it.

  3. There was a discussion some time ago about passing data between nodes via (flow) memory instead as via messages. The problem is that you don't visually see which data is flowing where (i.e. my blue arrows), without digging into the code of the nodes. Would it be useful to have the "setup pages layout" node to pass its layout (after storing it to flow memory) also via msg.payload to the "build mapping" node? Just an idea to make the data flow directions a bit more visual...

  4. What again was the delay node required?

But I am a big fan of your concept!!

1 Like

Bart,

The 2 nodes in the green group are only to allow for editing and updating the flow variables.
Once this is done nodes that depend on that data just read what they need from flow.context.

If changes are made to the layout etc, then I just click update, to refresh what is in the context, this also triggers the rebuild of the the mapping variable. I use the mapping variable to match hasp buttons to the devices that they control and any other useful info to avoid having to store it all on the hasp (memory saving)

So an incoming MQTT topic from e.g. my light bulb is matched to the corresponding hasp button, so its state can be updated, by changing the corresponding attributes, for the button style (on or off) This info all comes from the theme.

I only shared this aspect of the flow as its most complete, but the total flow will handle the whole process from configuring the HASP when it comes online to managing dynamic content updates, sending commands to control devices, updating the buttons to reflect current state of devices etc etc....

I'm very actively working on the code at the moment to make it more generic so it can be used by any type devices etc

I would suggest that when you are in a position to get your screen online, you give me a shout and I can send you the latest version, and talk you through it. :thinking:

I have decided to stick with this layout format as it seems to work well.

In efforts to further reduce the need for data entry, I have added functionality to read a "props" property in each level of the hierarchy.

This means that something like "btnStyle" can be added at the top level and will be applied to every button on that particular screen, without needing to enter it anywhere else.

But it can also be set at different levels, then it applies to items below that level. If set on page then all buttons on that page use the style, or at tab level then all buttons on that tab.

It can also be added at the button level so each button on a tab can look different if someone needs that :wink:

So in essence if a property is not set at a particular level, then it is inherited from a higher level.
I have also been setting some defaults up, so that if nothing is specified, the layout will still get generated with these default settings.

This method of cascading the property's down can reduce the data entry quite significantly, while allowing greater flexibility in the look of the generated output.

If there are any people using openHasp with NR, who would like to give it a try let me know.

 "1": { // name of hasp device (topic)
        props: {btnStyle: "btnscene" },
        page: [ //***************** start of pages ******************

            {   // ********* page 1 start **********
                props: {  }, // Page Settings
                tab: [
                    {  // *************** First tab start *******************
                        props: { }, // Tab 1 Settings 
                        btn: [
                            { text: "\n", value_str: "Main", extra: { topic: "loungelights/POWER2", type: "tas"  }, props: { btnStyle: "btntoggle" }},
                            { text: "\n", value_str: "Lamp", extra: { topic: "Bulb-20/POWER1", type: "tas", "color": true, name: "lounge_lamp" } },
                            { text: "\n", value_str: "Wall", extra: { topic: "loungelights/POWER1", type: "tas" } },
                            { text: "\n", value_str: "Dining", extra: { topic: "Bulb-10/POWER1", type: "tas", "color": true, name: "dining_lamp" } },
                            { text: "\n", value_str: "Fire", extra: { topic: "lounge_fire", type: "RF433" } },
                            { text: "\n", value_str: "Fish Pump", extra: { topic: "fish_pump", type: "RF433" } }
                        ]
                    }, // *************** First tab end *******************