Ok so here is a simple flow I made to allow 3 buttons on the openHasp plate to control 3 ESP based devices flashed with Tasmota firmware and provide state feedback on the display.
Follow instructions on openHasp to get a compatible screen flashed with firmware, connected to your MQTT server and network.
This layout is based on a 480x480 touch screen, so if yours is different you will need to adjust the size and position of the buttons to suit. As I wasn't keen on the built in switch widget, I decided to use a button and style it up a bit
Add this code to your pages.jsonl file on the openHasp file system.
{"page":1,"comment":"---------- page 1 ------- "}
{"page":1,"id":1,"obj":"btn","x":20,"y":250,"w":120,"h":160,"radius":15,"border_opa":255, "outline_width":3, "outline_pad":5, "outline_opa":0, "outline_color":"#E6523A","shadow_spread":15, "shadow_width":8, "shadow_color":"#E6523A", "shadow_opa":0, "text":" \uE91C\nWall","text_font":46,"text_color":"#FFFFFF"}
{"page":1,"id":2,"obj":"btn","x":180,"y":250,"w":120,"h":160,"radius":15,"border_opa":255, "outline_width":3, "outline_pad":5, "outline_opa":0, "outline_color":"#E6523A","shadow_spread":15, "shadow_width":8, "shadow_color":"#E6523A", "shadow_opa":0, "text":" \uF1E1\nMain","text_font":46,"text_color":"#FFFFFF"}
{"page":1,"id":3,"obj":"btn","x":340,"y":250,"w":120,"h":160,"radius":15,"border_opa":255, "outline_width":3, "outline_pad":5, "outline_opa":0, "outline_color":"#E6523A","shadow_spread":15, "shadow_width":8, "shadow_color":"#E6523A", "shadow_opa":0, "text":" \uE8DD\nLamp","text_font":46,"text_color":"#FFFFFF"}
What 1 switch json looks like -
{
"page": 1,
"id": 1,
"obj": "btn",
"x": 20,
"y": 250,
"w": 120,
"h": 160,
"radius": 15,
"border_opa": 255,
"outline_width": 3,
"outline_pad": 5,
"outline_opa": 0,
"outline_color": "#E6523A",
"shadow_spread": 15,
"shadow_width": 8,
"shadow_color": "#E6523A",
"shadow_opa": 0,
"text": " \uE91C\nWall", <--- Is the icon and text in the button
"text_font": 46,
"text_color": "#FFFFFF"
}
Change the text to suit your needs. The "page" and "id" properties define the page to display the object on (up to 12) and the id of the object. These are sent in the MQTT topic along with the event type when touched eg topic --> hasp/plate/state/p1b1 payload --> event: "up"
The other properties are similar to general web styling and define the look of the object.
In this case I have added an outline and shadow in red to indicate the button is ON. However the opacity is set to 0 so it cannot normally be seen.
When the screen starts up it will display the objects defined in the pages.jsonl file
Thereafter pressing a button will send the topic and payload as above.
To minimize the code I decided to use a lookup table, which translates the openHasp button topic into a smarthome device (Tasmota in my case) device topic. Since the flow is quite simple and doesn't know the state of the device yet, it sends a toggle command, so the device will just swap to the opposite of what it is now.
The next part of the flow listens for incoming messages from all my devices and then does a reverse lookup to find out which (if any) button needs to be updated with the current state of the device.
It then sends either the ON or OFF styling to the correct button. All this needs to do is update (in memory) the 3 opacity settings to either show or hide the red elements, and for good measure it sets the text colour white or yellow.
The result is this
You will need to set MQTT nodes to your own server and modify the mapping as required -
flow.set("mapping", {
"hasp/plate/state/p1b1": "cmnd/loungelights/POWER1",
"hasp/plate/state/p1b2": "cmnd/loungelights/POWER2",
"hasp/plate/state/p1b3": "cmnd/Bulb-20/POWER"
}
)
And here is the flow - More than happy to hear about any improvements etc, have fun
[{"id":"f4d4bb01dff95e2f","type":"mqtt in","z":"2df9d4297887de0c","name":"","topic":"hasp/plate/state/#","qos":"2","datatype":"auto-detect","broker":"","nl":false,"rap":true,"rh":0,"inputs":0,"x":345,"y":405,"wires":[["9a4c3bfad7332deb"]]},{"id":"9a4c3bfad7332deb","type":"switch","z":"2df9d4297887de0c","name":"Events only","property":"payload","propertyType":"msg","rules":[{"t":"hask","v":"event","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":575,"y":405,"wires":[["e9b69ba9e2757ce0"]]},{"id":"fcf07d14c9117350","type":"comment","z":"2df9d4297887de0c","name":"openHasp Designs","info":"{\"page\":1,\"comment\":\"---------- page 1 ------- \"}\n\n{\"page\":1,\"id\":1,\"obj\":\"btn\",\"x\":20,\"y\":250,\"w\":120,\"h\":160,\"radius\":15,\"border_opa\":255, \"outline_width\":3, \"outline_pad\":5, \"outline_opa\":0, \"outline_color\":\"#E6523A\",\"shadow_spread\":15, \"shadow_width\":8, \"shadow_color\":\"#E6523A\", \"shadow_opa\":0, \"text\":\" \\uE91C\\nWall\",\"text_font\":46,\"text_color\":\"#FFFFFF\"}\n{\"page\":1,\"id\":2,\"obj\":\"btn\",\"x\":180,\"y\":250,\"w\":120,\"h\":160,\"radius\":15,\"border_opa\":255, \"outline_width\":3, \"outline_pad\":5, \"outline_opa\":0, \"outline_color\":\"#E6523A\",\"shadow_spread\":15, \"shadow_width\":8, \"shadow_color\":\"#E6523A\", \"shadow_opa\":0, \"text\":\" \\uF1E1\\nMain\",\"text_font\":46,\"text_color\":\"#FFFFFF\"}\n{\"page\":1,\"id\":3,\"obj\":\"btn\",\"x\":340,\"y\":250,\"w\":120,\"h\":160,\"radius\":15,\"border_opa\":255, \"outline_width\":3, \"outline_pad\":5, \"outline_opa\":0, \"outline_color\":\"#E6523A\",\"shadow_spread\":15, \"shadow_width\":8, \"shadow_color\":\"#E6523A\", \"shadow_opa\":0, \"text\":\" \\uE8DD\\nLamp\",\"text_font\":46,\"text_color\":\"#FFFFFF\"}","x":360,"y":180,"wires":[]},{"id":"14ec6408c6aa050b","type":"function","z":"2df9d4297887de0c","name":"Build Control Msg","func":"const topics = flow.get(\"mapping\")\nif (topics[msg.topic] != null) {\n msg.topic = topics[msg.topic]\n msg.payload = \"toggle\"\n node.status({text:msg.topic});\n return msg;\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":930,"y":405,"wires":[["418084a44c0c3395","18ab92ab2cfd9104"]],"inputLabels":["from Plate"],"outputLabels":["to Device"]},{"id":"418084a44c0c3395","type":"mqtt out","z":"2df9d4297887de0c","name":"","topic":"","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"","x":1150,"y":510,"wires":[]},{"id":"ddcd2b715bcfb696","type":"mqtt in","z":"2df9d4297887de0c","name":"","topic":"stat/+/POWER","qos":"2","datatype":"auto-detect","broker":"","nl":false,"rap":true,"rh":0,"inputs":0,"x":690,"y":510,"wires":[["a8eaea43f8cc818a"]]},{"id":"a8eaea43f8cc818a","type":"function","z":"2df9d4297887de0c","name":"Update Plate Button","func":"const mapping = flow.get(\"mapping\")\nlet foundButton = null;\nlet page = 0\nlet id = 0\n\n// Modify incomming Tasmota topic to match outgoing topic,\n// so we can lookup the Hasp button that called it\nlet topic = msg.topic.split(\"/\")\nlet index = \"cmnd/\" + topic[1] + \"/\" + topic[2]\n\n// Reverse lookup plate topic\nfor (const button in mapping) {\n if (mapping[button] === index) {\n foundButton = button;\n break;\n }\n}\nif (foundButton === null) {\n node.warn(\"no matching button \" + msg.topic);\n return\n}\n\nlet parts = foundButton.split(\"/\")\nlet pxbx = parts[3] //page and id from matching topic\nmsg.topic = \"hasp/\" + parts[1] + \"/command/jsonl\" //hasp topic\nnode.status({ text: pxbx + \" \" + msg.payload });\n\nconst match = pxbx.match(/p(\\d+)b(\\d+)/);\nif (match) {\n page = match[1]; // Digits after 'p'\n id = match[2]; // Digits after 'b'\n}\n\n// Build json to send to hasp\n\nconst on = {\n \"page\": page,\n \"id\": id,\n \"border_opa\": 0,\n \"outline_opa\": 255,\n \"shadow_opa\": 160,\n \"text_color\": \"yellow\"\n}\nconst off = {\n \"page\": page,\n \"id\": id,\n \"border_opa\": 255,\n \"outline_opa\": 0,\n \"shadow_opa\": 0,\n \"text_color\": \"white\"\n}\n\n// Send on/off json to change button appearance on hasp\nmsg.payload = (msg.payload === \"ON\" ? on : off)\nreturn msg;\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":935,"y":570,"wires":[["418084a44c0c3395","6708a4c84af11fc2"]]},{"id":"3af8e42616953dc7","type":"mqtt in","z":"2df9d4297887de0c","name":"","topic":"stat/+/POWER1","qos":"2","datatype":"auto-detect","broker":"","nl":false,"rap":true,"rh":0,"inputs":0,"x":690,"y":570,"wires":[["a8eaea43f8cc818a"]]},{"id":"7638644341c466cf","type":"mqtt in","z":"2df9d4297887de0c","name":"","topic":"stat/+/POWER2","qos":"2","datatype":"auto-detect","broker":"","nl":false,"rap":true,"rh":0,"inputs":0,"x":690,"y":630,"wires":[["a8eaea43f8cc818a"]]},{"id":"18ab92ab2cfd9104","type":"debug","z":"2df9d4297887de0c","name":"debug 335","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1150,"y":405,"wires":[]},{"id":"a90206d314b0b942","type":"function","z":"2df9d4297887de0c","name":"create flow.mapping","func":"flow.set(\"mapping\", {\n \"hasp/plate/state/p1b1\": \"cmnd/loungelights/POWER1\",\n \"hasp/plate/state/p1b2\": \"cmnd/loungelights/POWER2\",\n \"hasp/plate/state/p1b3\": \"cmnd/Bulb-20/POWER\"\n}\n)\n\nreturn;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":530,"y":240,"wires":[[]]},{"id":"0f676d389b0b07fd","type":"inject","z":"2df9d4297887de0c","name":"1 >","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"1","topic":"","payload":"","payloadType":"date","x":335,"y":240,"wires":[["a90206d314b0b942"]]},{"id":"e9b69ba9e2757ce0","type":"switch","z":"2df9d4297887de0c","name":"up only","property":"payload.event","propertyType":"msg","rules":[{"t":"eq","v":"up","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":730,"y":405,"wires":[["14ec6408c6aa050b"]]},{"id":"6708a4c84af11fc2","type":"debug","z":"2df9d4297887de0c","name":"debug 336","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1150,"y":570,"wires":[]}]