Planning and setup - ESP32-S3 - 4848S040 - 480*480 IPS touchscreen

THIS IS A OPEN SOURCE PROJECT THERE ARE NO FEES

Here is what I did:

Install Firmware

  1. Connect display with your computer
  2. Flash the Firmware: Use Chrome Browser while you on the Nightly Build-Site Select “Guition ESP32-S3-4848S040” and Install
  3. Setting Up Wifi: Follow Instructions on the screen

Install HomeAssistant Components

  1. HACS: Install HACS if you haven’t already
  2. MQTT: Install and setup Home Assistant Add-on: Mosquitto broker if you haven’t already (OpenHasp and the Display communicate via MQTT)
  3. openHASP Custom Component: Install The OpenHASP Component from within HACS

Set up HomeAssistant

  1. Make sure your Display is running and reachable (Default setup shows with text “plate” on the top left corner of the screen and a popup with the actual IP-Adress).
  2. Go to shown IP-Adress with browser goto: http://your_ip/config/mqtt and fill in mqtt credentials from step 2 (see above)
  3. Got to shown IP-Adress with browser goto: http://your_ip/edit delete the pre-defined pages.jsonl file (Optional, otherwise Display loads this during startup)
  4. Go to shown IP-Adress with browser goto: http://your_ip/config/gpio and add:
    GPIO Output
    PIN: 40 (May be different on other Displays)
    TYPE: Power Relay
    Groupe: None
    Value: Normal
    I had issues with entity naming when I skipped this step
  5. SetUp your display within Homeassistant detailed information here and follow the example we’ll edit the files later on. If everything works as aspected the display will be auto-discovered by OpenHASP-Component
  6. Add a HA-Template sensor in configuration.yaml and edit to your needs (May use http://gridcalculator.dk/ to calculate col and gutter values)
template:
  - binary_sensor:
    - name: Display settings
      unique_id: 1b3a3dae-fd0c-4f47-9c8b-5b680478a977 # https://www.uuidgenerator.net/
      state: "{{ now() }}"
      attributes:
        display_w: "480"
        display_h: "480"
        grid_cols: "12" # increase for more resolution of object
        grid_rows: "12" # increase for more resolution of object
        grid_gutter: "8" # This looked nice for me
        grid_margin: "4" # This looked nice for me
        bg_color: "#000000"
        primary_color: "#4193d5"
        secondary_color: "#ee9f52"
        accent_color: "#A175C4"
        success_color: "#6cbe58"
        warning_color: "#d67430"
        error_color: "#e25628"
        radius: "4"

  1. If you’re done you should have a working display which is integrated in Homeassistant and comunicates via MQTT as well as a New Template Sensor with the given attributes
  2. Further reading if everything worked as aspected

Customizing

OpenHASP is highly customizable. Every Object can be set pixel perfect. This means you have to define every Width/Height x-and y-position.
To get rid of this I wrote I simple Jinja-Template. So I set Up dimensions with a grid system instead of pixel values. EXAMPLE:

Get Postion and size like this

{% import 'openhasp.jinja' as grid %} 
w = {{grid.w(1)}}
h = {{grid.h(1)}}
x = {{grid.x(1)}}
y = {{grid.y(1)}}

This Example generates the size (1x1) and coordinates ( 1st from left, 1ist from top) for the given display settings

Get colors like this

color = {% from 'openhasp.jinja' import color %}{{color('primary')}}
light_color = {% from 'openhasp.jinja' import color %}{{color('primary_light')}}
dark_color = {% from 'openhasp.jinja' import color %}{{color('primary_dark')}}

This Example generates Color Values. So if you want to change colors, just change the attribute of the Display settings sensor instead of changing the openhasp config

Try yourself

  1. In the folder “custom_templates” add a new file: openhasp.jinja
{# Get Postion and size like this
{% import 'openhasp.jinja' as grid %} 
w = {{grid.w(1)}}
h = {{grid.h(1)}}
x = {{grid.x(1)}}
y = {{grid.y(1)}}
#}

{# Konstanten für Default-Werte #}
{%- set DEFAULT_DISPLAY = {
    'width': 240,
    'height': 240,
    'cols': 6,
    'rows': 6,
    'margin': 10,
    'gutter': 10,
    'radius': 5
} -%}

{%- macro grid() -%}
    {%- set settings = 'binary_sensor.display_settings' -%}
    
    {# Display-Dimensionen #}
    {%- set display = {
        'width': state_attr(settings, 'display_w') | default(DEFAULT_DISPLAY.width) | int,
        'height': state_attr(settings, 'display_h') | default(DEFAULT_DISPLAY.height) | int,
        'cols': state_attr(settings, 'grid_cols') | default(DEFAULT_DISPLAY.cols) | int,
        'rows': state_attr(settings, 'grid_rows') | default(DEFAULT_DISPLAY.rows) | int,
        'margin': state_attr(settings, 'grid_margin') | default(DEFAULT_DISPLAY.margin) | int,
        'gutter': state_attr(settings, 'grid_gutter') | default(DEFAULT_DISPLAY.gutter) | int,
        'radius': state_attr(settings, 'radius') | default(DEFAULT_DISPLAY.radius) | int
    } -%}

    {# Berechnung der Zellgrößen #}
    {%- set cell = {
        'width': (display.width - (2 * display.margin) - ((display.cols-1) * display.gutter)) / display.cols,
        'height': (display.height - (2 * display.margin) - ((display.rows-1) * display.gutter)) / display.rows
    } -%}

    {{- {
        'w': cell.width,
        'h': cell.height,
        'cols': display.cols,
        'rows': display.rows,
        'margin': display.margin,
        'gutter': display.gutter,
        'display_width': display.width,
        'display_height': display.height,
        'radius': display.radius
    } | to_json -}}
{%- endmacro -%}

{%- macro w(w) -%}
    {%- set g = grid() | from_json -%}
    {%- set gutter = g.gutter if g.gutter is not none else 10 -%}
    {%- set cell_w = g.w if g.w is not none else 0 -%}
    {%- set cols = g.cols if g.cols is not none else 6 -%}
    {%- set errors = namespace(w=[]) %}
    {%- if w > cols or w <= -1 -%}
        {% set errors.w = 'cols must be between 1 and ' + cols | string %}
    {%- endif -%}
    {{- errors.w if errors.w | count > 0 else (w * cell_w | int) + ((w-1) * gutter | int) -}}    
{%- endmacro -%}

{%- macro h(h) -%}
    {%- set g = grid() | from_json -%}
    {%- set gutter = g.gutter if g.gutter is not none else 10 -%}
    {%- set cell_h = g.h if g.h is not none else 0 -%}
    {%- set rows = g.rows if g.rows is not none else 6 -%}
    {%- set errors = namespace(h=[]) %}
    {%- if h > rows or h <= -1 -%}
        {% set errors.h = 'rows must be between 1 and ' + rows | string %}
    {%- endif -%}
    {{- errors.h if errors.h | count > 0 else (h * cell_h | int) + ((h-1) * gutter | int) -}}    
{%- endmacro -%}

{%- macro x(x) -%}
    {%- set x = x - 1 -%}
    {%- set g = grid() | from_json -%}
    {%- set margin = g.margin if g.margin is not none else 10 -%}
    {%- set gutter = g.gutter if g.gutter is not none else 10 -%}
    {%- set width_raw = w(x) -%}
    {%- set width = width_raw | int(0) if width_raw is not none and width_raw | string | regex_match('^-?[0-9]+$') else 0 -%}
    {{- (margin if x == 0 else (margin + width + gutter)) | int -}}
{%- endmacro -%}

{%- macro y(y) -%}
    {%- set y = y - 1 -%}
    {%- set g = grid() | from_json -%}
    {%- set margin = g.margin if g.margin is not none else 10 -%}
    {%- set gutter = g.gutter if g.gutter is not none else 10 -%}
    {%- set height_raw = h(y) -%}
    {%- set height = height_raw | int(0) if height_raw is not none and height_raw | string | regex_match('^-?[0-9]+$') else 0 -%}
    {{- (margin if y == 0 else (margin + height + gutter)) | int -}}
{%- endmacro -%}

{# Get colors like this
color = {% from 'openhasp.jinja' import color %}{{color('primary')}}
light_color = {% from 'openhasp.jinja' import color %}{{color('primary_light')}}
dark_color = {% from 'openhasp.jinja' import color %}{{color('primary_dark')}}
#}

{%- macro AdjustColor(hex, brightness) -%}
    {%- set brightness = brightness|default(0.1) -%}
    {%- set hex = hex|replace('#', '')|lower -%}
    {%- if hex|length != 6 or not hex|regex_match('^[0-9a-f]{6}$') -%}
        {%- set hex = '000000' -%}
    {%- endif -%}
    {%- set color = namespace(rgb=[]) -%}
    {%- for i in range(0, 6, 2) -%}
        {%- set c = hex[i:i+2]|int(base=16, default=0) -%}
        {%- set c = (c + (c * brightness|float))|round|int -%}
        {%- if c < 0 %}{% set c = 0 %}{% endif -%}
        {%- if c > 255 %}{% set c = 255 %}{% endif -%}
        {%- set color.rgb = color.rgb + ["%02x"|format(c)] -%}
    {%- endfor -%}
    {{- '#' + ''.join(color.rgb) -}}
{%- endmacro -%}

{%- macro color(name) -%}
    {%- set parts = name.split('_') -%}
    {%- set base_color = state_attr('binary_sensor.display_settings', parts[0] + '_color') -%}
    {%- set base_color = '#000000' if base_color is none else base_color -%}
    {%- set base_color = base_color|replace('#', '')|lower -%}
    {%- if base_color|length != 6 or not base_color|regex_match('^[0-9a-f]{6}$') -%}
        {%- set base_color = '000000' -%}
    {%- endif -%}
    {%- set base_color = '#' + base_color -%}
    {%- if parts|length > 1 -%}
        {%- if parts[1] == 'light' -%}
            {{- AdjustColor(base_color, 0.2) -}}
        {%- elif parts[1] == 'dark' -%}
            {{- AdjustColor(base_color, -0.2) -}}
        {%- else -%}
            {{- base_color -}}
        {%- endif -%}
    {%- else -%}
        {{- base_color -}}
    {%- endif -%}
{%- endmacro -%}
  1. In your plate.json file:
[
    "SEITE 0 - SETTINGS",
    {
        "page": 1,
        "id": 0,
        "prev": 4
    },
    "SEITE 0 - PREVIOUS -BUTTON",
    {
        "page": 0,
        "id": 1,
        "obj": "btn",
        "outline_opa": 0,
        "border_opa": 0,
        "text_font": 32,
        "toggle": false,
        "action": "prev"
    },
    "SEITE 0 - HOME - BUTTON",
    {
        "page": 0,
        "id": 2,
        "obj": "btn",
        "outline_opa": 0,
        "border_opa": 0,
        "text_font": 32,
        "toggle": false,
        "action": {
            "down": "back",
            "hold": "restart"
        }
    },
    "SEITE 0 - NEXT - BUTTON",
    {
        "page": 0,
        "id": 3,
        "obj": "btn",
        "text": "\uE054",
        "outline_opa": 0,
        "border_opa": 0,
        "text_font": 32,
        "action": "next",
        "toggle": false
    },
    "SEITE 1 - CONTAINER",
    {
        "page": 1,
        "id": 2,
        "obj": "obj",
        "border_opa": 0,
        "bg_opa": 0,
        "click": 0
    }
]
  1. In your homeassistant configuration.yaml add:
openhasp:
  display02: # depends on you given device Name!!!
    objects:
      # SEITE 0
      - obj: "p0b0" # SETTINGS
        properties:
          "jsonl": >
            {% from 'openhasp.jinja' import color,grid,h,w,x,y %}
            {% set ns_colors = namespace(primary=color('primary')|default('#000000'), primary_light=color('primary_light')|default('#FFFFFF'), primary_dark=color('primary_dark')|default('#000000'), secondary=color('secondary')|default('#000000'), secondary_light=color('secondary_light')|default('#FFFFFF'), secondary_dark=color('secondary_dark')|default('#000000'), accent=color('accent')|default('#000000'), accent_light=color('accent_light')|default('#FFFFFF'), accent_dark=color('accent_dark')|default('#000000'), error=color('error')|default('#000000'), error_light=color('error_light')|default('#FFFFFF'), error_dark=color('error_dark')|default('#000000'), warning=color('warning')|default('#000000'), warning_light=color('warning_light')|default('#FFFFFF'), success=color('success')|default('#000000'), success_light=color('success_light')|default('#FFFFFF'), bg=color('bg')|default('#000000')) %}
            {{
                {
                  'bg_color' : ns_colors.bg
                } |to_json |string
              }}
      - obj: "p0b1" # PREVIOUS - BUTTON"
        properties:
          "jsonl": >
            {% from 'openhasp.jinja' import color,grid,h,w,x,y %}
            {% set ns = namespace(w=w(4), h=h(2), x=x(1), y=y(11), g=grid()) %}
            {% set ns_colors = namespace(primary=color('primary')|default('#000000'), primary_light=color('primary_light')|default('#FFFFFF'), primary_dark=color('primary_dark')|default('#000000'), secondary=color('secondary')|default('#000000'), secondary_light=color('secondary_light')|default('#FFFFFF'), secondary_dark=color('secondary_dark')|default('#000000'), accent=color('accent')|default('#000000'), accent_light=color('accent_light')|default('#FFFFFF'), accent_dark=color('accent_dark')|default('#000000'), error=color('error')|default('#000000'), error_light=color('error_light')|default('#FFFFFF'), error_dark=color('error_dark')|default('#000000'), warning=color('warning')|default('#000000'), warning_light=color('warning_light')|default('#FFFFFF'), success=color('success')|default('#000000'), success_light=color('success_light')|default('#FFFFFF'), bg=color('bg')|default('#000000')) %}
            {{
                {
                    'w': ns.w | default(0) | int,
                    'h': ns.h | default(0) | int,
                    'x': ns.x | default(0) | int,
                    'y': ns.y | default(0) | int,
                    'radius': (ns.g | default('{}') | from_json)['radius'] | default(0) | int,
                    'bg_color': ns_colors.primary,
                    'bg_color02': ns_colors.primary_light,
                    'text': "\uE04D"
                  } |to_json |string
            }}
      - obj: "p0b2" # HOME - BUTTON
        properties:
          "jsonl": >
            {% from 'openhasp.jinja' import grid,w,h,x,y,color %}
            {% set ns = namespace(w=w(4), h=h(2), x=x(5), y=y(11), g=grid()) %}
            {% set ns_colors = namespace(primary=color('primary')|default('#000000'), primary_light=color('primary_light')|default('#FFFFFF'), primary_dark=color('primary_dark')|default('#000000'), secondary=color('secondary')|default('#000000'), secondary_light=color('secondary_light')|default('#FFFFFF'), secondary_dark=color('secondary_dark')|default('#000000'), accent=color('accent')|default('#000000'), accent_light=color('accent_light')|default('#FFFFFF'), accent_dark=color('accent_dark')|default('#000000'), error=color('error')|default('#000000'), error_light=color('error_light')|default('#FFFFFF'), error_dark=color('error_dark')|default('#000000'), warning=color('warning')|default('#000000'), warning_light=color('warning_light')|default('#FFFFFF'), success=color('success')|default('#000000'), success_light=color('success_light')|default('#FFFFFF'), bg=color('bg')|default('#000000')) %}
            {{
                {
                    'w': ns.w | default(0) | int,
                    'h': ns.h | default(0) | int,
                    'x': ns.x | default(0) | int,
                    'y': ns.y | default(0) | int,
                    'radius': (ns.g | default('{}') | from_json)['radius'] | default(0) | int,
                    'bg_color': ns_colors.primary,
                    'bg_color02': ns_colors.primary_light,
                    'text': "\uE6A1"
                  } |to_json |string
            }}
      - obj: "p0b3" # NEXT - BUTTON
        properties:
          "jsonl": >
            {% from 'openhasp.jinja' import grid,w,h,x,y,color %}
            {% set ns = namespace(w=w(4), h=h(2), x=x(9), y=y(11), g=grid()) %}
            {% set ns_colors = namespace(primary=color('primary')|default('#000000'), primary_light=color('primary_light')|default('#FFFFFF'), primary_dark=color('primary_dark')|default('#000000'), secondary=color('secondary')|default('#000000'), secondary_light=color('secondary_light')|default('#FFFFFF'), secondary_dark=color('secondary_dark')|default('#000000'), accent=color('accent')|default('#000000'), accent_light=color('accent_light')|default('#FFFFFF'), accent_dark=color('accent_dark')|default('#000000'), error=color('error')|default('#000000'), error_light=color('error_light')|default('#FFFFFF'), error_dark=color('error_dark')|default('#000000'), warning=color('warning')|default('#000000'), warning_light=color('warning_light')|default('#FFFFFF'), success=color('success')|default('#000000'), success_light=color('success_light')|default('#FFFFFF'), bg=color('bg')|default('#000000')) %}
            {{
                {
                    'w': ns.w | default(0) | int,
                    'h': ns.h | default(0) | int,
                    'x': ns.x | default(0) | int,
                    'y': ns.y | default(0) | int,
                    'radius': (ns.g | default('{}') | from_json)['radius'] | default(0) | int,
                    'bg_color': ns_colors.primary,
                    'bg_color02': ns_colors.primary_light,
                    'text': "\uE054"
                  } |to_json |string
            }}

      # SEITE 1
      - obj: "p1b0" # SETTINGS
        properties:
          "jsonl": >
            {% from 'openhasp.jinja' import color,grid,h %}
            {% set ns_colors = namespace(primary=color('primary')|default('#000000'), primary_light=color('primary_light')|default('#FFFFFF'), primary_dark=color('primary_dark')|default('#000000'), secondary=color('secondary')|default('#000000'), secondary_light=color('secondary_light')|default('#FFFFFF'), secondary_dark=color('secondary_dark')|default('#000000'), accent=color('accent')|default('#000000'), accent_light=color('accent_light')|default('#FFFFFF'), accent_dark=color('accent_dark')|default('#000000'), error=color('error')|default('#000000'), error_light=color('error_light')|default('#FFFFFF'), error_dark=color('error_dark')|default('#000000'), warning=color('warning')|default('#000000'), warning_light=color('warning_light')|default('#FFFFFF'), success=color('success')|default('#000000'), success_light=color('success_light')|default('#FFFFFF'), bg=color('bg')|default('#000000')) %}
            {{
                {
                  'bg_color': ns_colors.bg
                } |to_json |string
            }}
      - obj: "p1b2" # CONTAINER
        properties:
          "jsonl": >
            {% from 'openhasp.jinja' import color,grid,h,w,x,y %}
            {% set ns = namespace(h=h(10), g=grid()) %}
            {{
                {
                    'w': (grid() | default('{}') | from_json)['display_width'] | default(0) | int,
                    'h': (h(10) if h(10) is not none else 0) | int,
                    'x': 0| default(0) | int,
                    'y': 0| default(0) | int
                } |to_json |string
            }}
      
  1. Restart The Display and/or Open your Home Assistant instance and show your service developer tools with a specific service selected. reload_config_entry for your device to see your changes

  2. From now on you’re on your own. Feel free to share yours

Here is my display:





14 Likes