HASPmota ~
This feature is included in tasmota32-lvgl.bin
Tasmota is happy to support openHASP compatible format (named HASPmota), which allows to describe rich graphics interfaces using simple JSON templates. HASPmota support leverages the power of LVGL and the Berry language, but doesn't need to code nor learn the LVGL API.
This feature is heavily inspired from @fvanroie's openHASP project.
Minimal requirements~
Hardware: HASPmota is supported on all ESP32 variants, and requires a display configured with universal display (using display.ini
or autoconf
). When they are correctly configured, you should see a splash screen at startup.
Currently PSRAM is strongly recommended to run HASPmota.
Firmware: HASPmota is included in tasmota32-lvgl
firmwares.
Quick tour~
You can see HASPmota in action in a couple of minutes.
Upload tamota_demo.tapp
to your file system. The easy way is to use the following command in Tasmota console:
UrlFetch https://raw.githubusercontent.com/arendst/Tasmota/development/tasmota/berry/haspmota/haspmota_demo.tapp
Restart 1
to restart- Enjoy
You should see the following screen, and click on buttons to switch to the second screen:
Understanding the template~
HASPmota automatically loads the template from a file named pages.jsonl
. This file contains JSON Lines, i.e. a single JSON document per line. Each line describes an element on the screen. Elements are grouped into pages.
Page 0
contains objects that are displayed on all screens. They are typically used for headers and menus.
The lines below define the header label (red background) and the clock and wifi widgets.
{"page":0,"comment":"---------- Upper stat line ----------"}
{"id":11,"obj":"label","x":0,"y":0,"w":320,"pad_right":90,"h":22,"bg_color":"#D00000","bg_opa":255,"radius":0,"border_side":0,"text":"Tasmota","text_font":"montserrat-20"}
{"id":15,"obj":"lv_wifi_arcs","x":291,"y":0,"w":29,"h":22,"radius":0,"border_side":0,"bg_color":"#000000","line_color":"#FFFFFF"}
{"id":16,"obj":"lv_clock","x":232,"y":3,"w":55,"h":16,"radius":0,"border_side":0}
The lines below describe the 3 buttons at the bottom, and their respective actions.
{"comment":"---------- Bottom buttons - prev/home/next ----------"}
{"id":101,"obj":"btn","x":20,"y":210,"w":80,"h":25,"action":"prev","bg_color":"#1fa3ec","radius":10,"border_side":1,"text":"\uF053","text_font":"montserrat-20"}
{"id":102,"obj":"btn","x":120,"y":210,"w":80,"h":25,"action":"back","bg_color":"#1fa3ec","radius":10,"border_side":1,"text":"\uF015","text_font":"montserrat-20"}
{"id":103,"obj":"btn","x":220,"y":210,"w":80,"h":25,"action":"next","bg_color":"#1fa3ec","radius":10,"border_side":1,"text":"\uF054","text_font":"montserrat-20"}
Page 1
is the default page, and contains different widgets types: labels and arc. The values are changed at runtime via Tasmota's rule system, see below for details.
{"page":1,"comment":"---------- Page 1 ----------"}
{"id":0,"bg_color":"#0000A0","bg_grad_color":"#000000","bg_grad_dir":1,"text_color":"#FFFFFF"}
{"id":2,"obj":"arc","x":20,"y":65,"w":80,"h":100,"border_side":0,"type":0,"rotation":0,"start_angle":180,"end_angle":0,"start_angle1":180,"value_font":12,"value_ofs_x":0,"value_ofs_y":-14,"bg_opa":0,"text":"--.-°C","min":200,"max":800,"val":0,"val_rule":"ESP32#Temperature","val_rule_formula":"val * 10","text_rule":"ESP32#Temperature","text_rule_format":"%2.1f °C"}
{"id":5,"obj":"label","x":2,"y":35,"w":120,"text":"Temperature","align":1}
{"id":10,"obj":"label","x":172,"y":35,"w":140,"text":"MPU","align":0}
{"id":11,"obj":"label","x":172,"y":55,"w":140,"text":"x=","align":0,"text_rule":"MPU9250#AX","text_rule_format":"x=%6.3f","text_rule_formula":"val / 1000"}
{"id":12,"obj":"label","x":172,"y":75,"w":140,"text":"y=","align":0,"text_rule":"MPU9250#AY","text_rule_format":"y=%6.3f","text_rule_formula":"val / 1000"}
{"id":13,"obj":"label","x":172,"y":95,"w":140,"text":"z=","align":0,"text_rule":"MPU9250#AZ","text_rule_format":"z=%6.3f","text_rule_formula":"val / 1000"}
Page 2
contains custom widgets as Berry code. These widgets are imported within haspmota_widgets.tapp
{"page":2,"comment":"---------- Page 2 ----------"}
{"id":0,"bg_color":"#0000A0","bg_grad_color":"#000000","bg_grad_dir":1,"text_color":"#FFFFFF"}
{"comment":"---------- Wifi status ----------"}
{"id":20,"obj":"lv_wifi_graph","x":257,"y":25,"w":60,"h":40,"radius":0}
{"id":21,"obj":"lv_tasmota_info","x":3,"y":25,"w":251,"h":40,"radius":0}
{"id":22,"obj":"lv_tasmota_log","x":3,"y":68,"w":314,"h":90,"radius":0,"text_font":12}
Finally, the following line allows to run arbitrary Berry code.
{"comment":"--- Trigger sensors every 2 seconds ---","berry_run":"tasmota.add_cron('*/2 * * * * *', def () tasmota.publish_rule(tasmota.read_sensors()) end, 'oh_every_5_s')"}
The code trigger a read of sensors every 2 seconds and publish the JSON result to be parsed by the rule engine.
tasmota.add_cron('*/2 * * * * *', def () tasmota.publish_rule(tasmota.read_sensors()) end, 'oh_every_5_s')
Running HASPmota~
HASPmota
code is included in tasmota32-lvgl
firmwares.
Running HASPmota
with your own template is as simple as:
- create a template in
pages.jsonl
and store it in the Tasmota file system - create an
autoexec.be
file containing the following:
# simple `autoexec.be` to run HASPmota using the default `pages.jsonl`
import haspmota
haspmota.start()
HASPmota reference~
Integration to Berry~
objects as p<x>b<y>
~
Each HASPmota widget is mapped to a global variable of name p<x>b<y>
. Example: p1b10
. Such objects can be directly used via their attributes.
Example:
p1b10.x += 10
p1b10.text = "Hello"
print(p1b10.w)
pages as p<x>
~
Pages objects are mapped to a global variable of name p<x>
.
Changing pages can be done with p2.show()
additional parsing~
HASPmota parses all lines from the file pages.jsonl
. You can dynamically add new objects as JSON with haspmota.parse(<json>)
. This functions takes a single json line. It is highly recommended to specify the page id in the json, otherwise the object is added to the current page.
Pages~
Pages object are identified by object if 0
. Example:
{"page":1,"id":0,"bg_color":"#0000A0","bg_grad_color":"#000000","bg_grad_dir":1,"text_color":"#FFFFFF"}
Internally HASPmota pages are implemented with LVGL screens, i.e. a parent object.
Page 0
is a special page that is displays over every screens. It is the perfect place to put navigation menus. It is implement as lv.layer_top()
.
Page 1
is always present and the default page.
Attributes specific to page | Details |
---|---|
prev | (int) target page number when pressing PREV button |
next | (int) target page number when pressing NEXT button |
home | (int) target page number when pressing HOME button |
And generally all object attributes |
Classes of widgets~
Attribute "obj"
can take the following values:
HASPmota Class | Embedded LVGL class |
---|---|
obj | lv.obj |
btn | lv.btn |
switch | lv.switch |
checkbox | lv.checkbox |
label | lv.label |
spinner | lv.spinner |
line | lv.line |
img | lv.img |
dropdown | lv.dropdown |
roller | lv.roller |
btnmatrix | lv.btnmatrix |
bar | lv.bar |
slider | lv.slider |
arc | lv.arc |
textarea | lv.textarea |
qrcode | lv.qrcode |
You can also import custom widget as long as they inherit from lv.obj
and the class name matches the module name.
Example: "obj":"lv_wifi_graph"
will trigger the following: - import lv_wifi_graph
- instantiation of lv_wifi_graph(parent)
object - if successful, it can be used like a typical HASPmota object
Attributes~
Below are the standard attributes:
Attribute name | LVGL equivalent | Details |
---|---|---|
comment | Ignored | |
meta | any | Any data, can be retrieved from Berry code with p<x>b<y>.meta |
page | 0 to 15 Parent screen object | Page id for the current object. If not present, takes the value of the current page |
id | 0..255 | Id number of the object. Id 0 means the entire page.A global berry object is created with name p<page>b<id> (ex: p1b10 ) |
obj | widget class | Class of the widget (see above). If not present, the entire JSON line is ignored |
action | "next" , "prev" , "back" or "p<x>" | Switch to page when the object is touched |
x | x | X coordinate of top left corner |
y | y | Y coordinate of top left corner (Y is pointing down) |
h | height | Height in pixels |
w | width | Width in pixels |
hidden | flag lv.OBJ_FLAG_HIDDEN | Object is hidden (bool) |
enabled | flag lv.OBJ_FLAG_CLICKABLE | Object is touch/clickable (bool) |
click | flag lv.OBJ_FLAG_CLICKABLE | Synonym of enabled . Object is touch/clickable (bool) |
toggle | flag lv.STATE_CHECKED | When enabled, creates a toggle-on/toggle-off button. If false, creates a normal button. TODO check if bool or string |
radius | style_radius | Radius of rounded corners |
bg_opa | style_bg_opa | Opacity: 0 is transparent, 255 is opaque |
bg_color | style_bg_color | Color of background, format is #RRGGBB |
bg_grad_color | style_bg_grad_color | Color of background gradient |
bg_grad_dir | style_bg_grad_dir | Gradient direction0 : none1 : Vertical (top to bottom) gradient2 : Horizontal (left to right) gradient |
border_side | style_border_side | Borders to be displayed (add all values)0 : none1 : bottom2 : top4 : left8 : bottom15 : full (all 4) |
border_width | style_border_width | Width of border in pixels |
border_color | style_border_color | |
line_color | style_line_color | Color of line |
line_width | style_line_width | |
line_width1 | style_arc_width | Sets the line width of an arc indicator part |
pad_left | style_pad_left | Left padding in pixels |
pad_right | style_pad_right | Right padding in pixels |
pad_top | style_pad_top | Top padding in pixels |
pad_bottom | style_pad_bottom | Bottom padding in pixels |
pad_all | style_pad_all | Sets all 4 padding values at once (Write-only) |
Attributes related to text content~
Attribute name | LVGL equivalent | Details |
---|---|---|
text | text | Sets the inner text of the object. If the native LVGL object does not support text (like lv.btn ), a lv.label sub-object is automatically created. |
value_str | text | Synonym of text |
align | style_text_align | Set alignment for text0 or "left" : lv.TEXT_ALIGN_LEFT 1 or "center" : lv.TEXT_ALIGN_CENTER 2 or "right" : lv.TEXT_ALIGN_RIGHT |
text_font | style_text_font | Sets the font name and size for the text. If int , the default font is robotocondensed_latin1 and the parameter sets the sizeIf string , the font is in the form <font_name>-<font_size> , example: montserrat-20 or in the form A:<font_file> to load a binary font from the file-system. |
value_font | style_text_font | Synonym of text_font |
text_color | style_text_color | Sets the color of text |
value_color | style_text_color | Synonym of text_color |
value_ofs_x | x of sub-label | Sets the X offset in pixels within the object |
value_ofs_y | y of sub-label | Sets the Y offset in pixels within the object |
text_rule | Link the text to a Tasmota rule, see below | |
text_rule_formula | Link the text to a Tasmota rule, see below | |
text_rule_format | Link the text to a Tasmota rule, see below |
Attributes related to values~
Attribute name | LVGL equivalent | Details |
---|---|---|
min | range | Set the minimum value of range (int) |
max | range | Set the maximum value of range (int) |
val | value | Set the value (int) |
val_rule | Link a value to a Tasmota rule, see below | |
val_rule_formula | Link a value to a Tasmota rule, see below |
Attributes specific to switch
~
Attribute name | LVGL equivalent | Details |
---|---|---|
bg_color10 | bg_color | Color of the indicator when the switch is selected. Otherwise it uses bg_color . |
bg_color20 | bg_color | Color of the knob. |
radius20 | radius | Radius of the knob. |
Attributes specific to arc
~
Attribute name | LVGL equivalent | Details |
---|---|---|
start_angle | bg_start_angle | Start angle of the arc background. Angles are in degrees in [0;360] range. Zero degrees is at the middle right (3 o'clock) of the object and the degrees are increasing in clockwise direction. |
end_angle | bg_end_angle | End angle of the arc background. |
start_angle1 | start_angle | Start angle of the arc indicator. |
end_angle1 | end_angle | End angle of the arc indicator. |
rotation | rotation | Offset to the 0 degree position |
type | mode | Sets the arc mode0 : lv.ARC_MODE_NORMAL 1 : lv.ARC_MODE_REVERSE 2 : lv.ARC_MODE_SYMMETRICAL |
pad_top2 | style_pad_top | Top padding for lv.PART_KNOB part |
pad_bottom2 | style_pad_bottom | Bottom padding for lv.PART_KNOB part |
pad_left2 | style_pad_left | Left padding for lv.PART_KNOB part |
pad_right2 | style_pad_right | Right padding for lv.PART_KNOB part |
pad_all2 | style_pad_all | Set all 4 padding for lv.PART_KNOB part (write-only) |
radius2 | style_radius | Radius for lv.PART_KNOB part |
Attributes specific to img
~
Attribute name | LVGL equivalent | Details |
---|---|---|
src | src | Path to the image in the file-system |
image_recolor | style_image_recolor | Color used to recolor the image |
image_recolor_opa | style_image_recolor_opa | Opacity of image recoloring |
angle | angle | Angle of the image, in 1/10th of degrees. Range 0..3600. |
Attributes specific to roller
~
Attribute name | LVGL equivalent | Details |
---|---|---|
text | selected_text | (read-only) Get the text of the currently selected item. The string is truncated to the first 256 bytes. |
Attributes specific to spinner
~
Attribute name | LVGL equivalent | Details |
---|---|---|
angle | The length of the spinning segment in degrees - can be changed after initial value since v12.1.1.1 | |
speed | The time for 1 turn in ms - can be changed after initial value since v12.1.1.1 |
Attributes specific to qrcode
~
Attribute name | LVGL equivalent | Details |
---|---|---|
qr_size | Size in pixels of the QRCode, always square (height = width). This cannot be changed once the object is created. | |
qr_dark_color | Color used for the dark pixels. This cannot be changed once the object is created. | |
qr_light_color | Color used for the light pixels. This cannot be changed once the object is created. | |
qr_text | (mandatory) String to encode as a QRCode, can be changed at any moment. |
HASPmota styling~
Since v13.2.0.1, HASPmota includes styling extensions replicating OpenHASP Styling Properties.
Styling attributes can include a 2-digit suffix to indicate which element and which state the styling attributes refers to:
The part indexes are:
- 00 = main part of the object (i.e. the background)
- 10 = the indicator or needle, highlighting the the current value
- 20 = the knob which can be used the change the value
- 30 = the background of the items/buttons
- 40 = the items/buttons (not implemented yet)
- 50 = the selected item
- 60 = major ticks of the gauge object
- 70 = the text cursor
- 80 = the scrollbar
- 90 = other special part, not listed above
The state indexes:
- 00 = default styling
- 01 = styling for toggled state
- 02 = styling for pressed, not toggled state
- 03 = styling for pressed and toggled state
- 04 = styling for disabled not toggled state
- 05 = styling for disabled and toggled state
A toggle
btn
can be made to show text
yellow
when toggled ON with "text_color":"white","text_color01":"yellow"
The following attributes accept styling suffix:
radius
border_side
border_width
border_color
line_width
bg_color
bg_grad_color
bg_grad_dir
line_color
pad_left
pad_right
pad_top
pad_bottom
pad_all
text_color
Tasmota extensions~
Update sensor value via rules~
You can automatically adjust attributes val
and text
from sensor values via a simple rule engine.
attribute | description |
---|---|
val_rule | Rule pattern to trigger an update of the val attribute.Example: "val_rule":"ESP32#Temperature" |
val_rule_formula | Optional expression (using Berry) to transform the value extracted from the rule to the value pushed to val . This typically allows to adjust ranges of values.Input value is always converted to float not int . The input value is named val .Example: "val_rule_formula":"val / 1000" |
Changing a text
attribute from rules:
attribute | description |
---|---|
text_rule | Rule pattern to trigger an update of the text attribute.Example: "text_rule":"ESP32#Temperature" |
text_rule_formula | Optional expression (using Berry) to transform the value extracted from the rule to the value pushed to val . This typically allows to adjust ranges of values.Input value is always converted to float not int . The input value is named val .Example: "val_rule_formula":"val * 10" |
text_rule_format | String format of the result string. The format uses Berry's string.format() , which is a subset of printf format.Example: "text_rule_format":"%2.1f °C" |
React to user actions~
Every time the user touches an active element on the screen, HASPmota publishes internal events you can listen and react to. For example if you press a button p1b10
, HASPmota publishes an event {"hasp":{"p1b10":{"event":"up"}}
when the button is released. You can easily create a rule to react to this event.
Example:
tasmota.add_rule("hasp#p1b10#event=up", / -> print("Button p1b10 pressed"))
Events with value changed
indicate that the value of the widgets has changed. Events are only fired if the change in value comes from a screen interaction. No event is fired when the value was changed programmatically. The new value is sent as val
attribute, and for specific widgets (roller) a text
attribute is sent for the label of the value. {"hasp":{"p1b1":{"val":3,"text":"A3","event":"changed"}}}
Example:
tasmota.add_rule("hasp#p1b1#text", / text-> print("p1b1 text value=", text))
Run arbitrary Berry code~
Inserting an attribute berry_run
to any object will compile and run the embedded Berry code right after widget initialization. If you need the current widget object, it requires a special construct like: "berry_run":"return def (x) print(x) end"
One common use is to trigger sensors read every 2 seconds:
{"comment":"--- Trigger sensors every 2 seconds ---","berry_run":"tasmota.add_cron('*/2 * * * * *', def () tasmota.publish_rule(tasmota.read_sensors()) end, 'oh_every_5_s')"}
HASPmota fonts~
HASPmota can use 3 types of LVGL fonts:
- embedded fonts, i.e. fonts included in Tasmota firmware
- binary bitmat fonts (extension
.lvfont
), stored in the Tasmota file-system usinglv_font_conv
tool. Tasmota includes various fonts pre-converted - TrueType fonts (extension
.ttf
), stored in the Tasmota file-system. TrueType vector fonts can be scaled at any size and render well at high font-sizes, but require significant PSRAM memory
Embedded fonts~
Use attribute "text_font":"unscii-8"
. The general form is "text_font":"<font_name>-<font_size>"
Embedded font | Details |
---|---|
montserrat-10 montserrat-14 montserrat-20 montserrat-28 | Default LVGL normal font, including icons |
unscii-8 unscii-16 | Default LVGL, 8 px and 16 px pixel perfect font with only ASCII characters |
seg7-8 seg7-10 seg7-12 seg7-14 seg7-16 seg7-18 seg7-20 seg7-24 seg7-28 seg7-36 seg7-48 | 7 segments display, contains digits, space, ':' and '!' for a space of the size of ':' Sizes 8, 10, 12, 14, 16, 18 are pixel-perfect bold Sizes 20, 24, 28, 36, 48 are dithered (2 bits per pixel) bold-italic |
robotocondensed-12 robotocondensed-16 robotocondensed-24 | Default OpenHASP |
Binary bitmap fonts~
Files of extension .lvfont
, they are similar to embedded fonts but can be loaded dynamically from the file-system.
Generally speaking, binary bitmap fonts are mostly used for low resolution font, or they take too much memory. For larger font, prefer TrueType fonts (see below).
Pixel-perfect fonts~
Pixel-perfect fonts don't use dithering and are specially designed for low resolution monochrome displays.
example:
Same image zoomed x2
unscii-8
(bottom font) is embedded by default.
To use the other fonts, add the corresponding file to the Tasmota file system.
Fonts below are from KreativeKorp
Berkelium64.lvfont
Berkelium (BSW) GEOS System FontPrintChar21.lvfont
The Ultimate Apple II FontShaston320.lvfont
Shaston GS/OS System Font
Fonts below are from Daniel Linssen
m5x7.lvfont
https://managore.itch.io/m5x7m3x6.lvfont
https://managore.itch.io/m3x6
Here is the HASPmota template for the screen above:
{"page":0,"comment":"---------- Upper stat line ----------"}
{"id":0,"text_color":"#FFFFFF"}
{"id":11,"obj":"label","x":0,"y":0,"w":320,"pad_right":90,"h":22,"bg_color":"#D00000","bg_opa":255,"radius":0,"border_side":0,"text":"Tasmota","text_font":"montserrat-20"}
{"id":15,"obj":"lv_wifi_arcs","x":291,"y":0,"w":29,"h":22,"radius":0,"border_side":0,"bg_color":"#000000","line_color":"#FFFFFF"}
{"id":16,"obj":"lv_clock","x":232,"y":3,"w":55,"h":16,"radius":0,"border_side":0}
{"page":1,"comment":"---------- Page 1 ----------"}
{"id":5,"obj":"label","x":2,"y":30,"w":316,"text":"Berkelium 74192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:Berkelium64.lvfont"}
{"id":6,"obj":"label","x":2,"y":55,"w":316,"text":"PrintChar21 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:PrintChar21.lvfont"}
{"id":7,"obj":"label","x":2,"y":80,"w":316,"text":"Shaston320 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:Shaston320.lvfont"}
{"id":8,"obj":"label","x":2,"y":105,"w":316,"text":"m5x7 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:m5x7.lvfont"}
{"id":9,"obj":"label","x":2,"y":130,"w":316,"text":"m3x6 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"A:m3x6.lvfont"}
{"id":99,"obj":"label","x":2,"y":170,"w":316,"text":"unscii-8 192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"unscii-8"}
TrueType fonts~
Add support for TTF fonts in HASPmota. The attributes needs to specify the font name and the size "text_font":"sketchbook.ttf-32"
Example:
Related pages.jsonl
file:
{"page":0,"comment":"---------- Upper stat line ----------"}
{"id":0,"text_color":"#FFFFFF"}
{"id":11,"obj":"label","x":0,"y":0,"w":320,"pad_right":90,"h":22,"bg_color":"#D00000","bg_opa":255,"radius":0,"border_side":0,"text":"Tasmota","text_font":"montserrat-20"}
{"id":15,"obj":"lv_wifi_arcs","x":291,"y":0,"w":29,"h":22,"radius":0,"border_side":0,"bg_color":"#000000","line_color":"#FFFFFF"}
{"id":16,"obj":"lv_clock","x":232,"y":3,"w":55,"h":16,"radius":0,"border_side":0}
{"page":1,"comment":"---------- Page 1 ----------"}
{"id":1,"obj":"label","x":2,"y":40,"w":316,"text":"sketchbook-32\n192.168.x.x ABCDEF\nThe quick brown fox jumps over the lazy dog","text_font":"sketchbook.ttf-32"}